import os
import pygame
import re
import time
import json
import os
import subprocess
import platform
import sys
import signal
import queue  # 添加queue模块用于进程管理
import threading  # 添加threading模块用于进程管理
import random  # 添加random模块用于随机播放功能
from mutagen.id3 import ID3, ID3NoHeaderError
from mutagen.mp3 import MP3
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from tkinter import scrolledtext
import cv2
import logging
import chardet

# 导入歌词抓取器
from lyrics_fetcher import LyricsFetcher

# 配置日志记录
logging.basicConfig(
    level=logging.DEBUG, # 日志级别
    format='%(asctime)s - %(levelname)s - %(message)s',
    # format='%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',  # 日期时间格式
    handlers=[
        logging.FileHandler("run.log", encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 尝试导入pywin32模块用于Windows上的音量控制
# 这些模块仅用于Windows平台下向ffplay发送键盘命令
try:
    import win32api
    import win32gui
    import win32con
    HAS_WIN32 = True
except ImportError:
    HAS_WIN32 = False
    logger.warning("未找到pywin32模块，动态音量调节功能可能不可用")

# 自定义Tooltip类，用于显示鼠标悬停提示
class Tooltip:
    def __init__(self, widget):
        """创建Tooltip对象"""
        self.widget = widget
        self.tooltip_window = None
        self.text = ""
        self.widget.bind("<Enter>", self.show_tooltip)
        self.widget.bind("<Leave>", self.hide_tooltip)
        self.widget.bind("<Configure>", self.hide_tooltip)  # 当窗口大小改变时隐藏tooltip
    
    def show_tooltip(self, event=None):
        """显示Tooltip"""
        if self.tooltip_window or not self.text:
            return
        
        # 计算tooltip位置
        x, y, _, _ = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 25
        y = y + self.widget.winfo_rooty() + 25
        
        # 创建tooltip窗口
        self.tooltip_window = tk.Toplevel(self.widget)
        self.tooltip_window.wm_overrideredirect(True)  # 无边框窗口
        self.tooltip_window.wm_geometry(f"+{x}+{y}")
        
        # 创建tooltip内容
        label = tk.Label(self.tooltip_window, text=self.text, 
                        background="#ffffe0", relief="solid", borderwidth=1,
                        font=("SimHei", 9))
        label.pack(ipadx=2)
    
    def hide_tooltip(self, event=None):
        """隐藏Tooltip"""
        if self.tooltip_window:
            self.tooltip_window.destroy()
            self.tooltip_window = None
    
    def configure(self, text=""):
        """配置Tooltip文本"""
        self.text = text
        # 如果tooltip窗口已存在，更新其内容
        if hasattr(self, 'tooltip_window') and self.tooltip_window:
            for child in self.tooltip_window.winfo_children():
                if isinstance(child, tk.Label):
                    child.config(text=text)

# 定义支持的媒体文件扩展名
audio_extensions = ['.mp3', '.wav', '.flac', '.ogg', '.m4a', '.aac']
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.m4v']
media_extensions = audio_extensions + video_extensions

# 配置文件路径 - 与播放器放在同一目录、打包后指向exe所在目录
if getattr(sys, 'frozen', False):
    # 程序被打包成exe文件
    CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), '.config.json')
else:
    # 程序在开发环境中运行
    CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.config.json')

class AudioVideoPlayer:
    def __init__(self, root):
        """初始化音频视频播放器"""
        self.root = root
        self.root.title("音频播放器")
        self.root.resizable(True, True)
        
        # 窗口默认尺寸常量
        self.DEFAULT_WIDTH = 800
        self.DEFAULT_HEIGHT = 600
        
        # 集中加载配置文件数据到内存
        self.config_data = None
        self._load_config_data()
        
        # 从内存中的配置数据加载窗口位置和大小
        x, y, width, height = self._load_window_config_from_memory()
        
        # 一次性设置窗口大小和位置
        self.root.geometry(f"{width}x{height}+{x}+{y}")

        # 设置窗口图标
        self.root.iconbitmap('favicon.ico') # iconbitmap 方法仅支持 .ico、.xbm 或 .bmp 格式的文件（在 Windows 上），而在其他平台上有所不同。
        
        # 记录初始窗口位置和大小
        self.last_x = x
        self.last_y = y
        self.last_width = width
        self.last_height = height
        
        # 添加标记来跟踪用户是否主动调整了窗口大小
        self.user_resized_height = False
        
        # 设置中文字体
        self.font = ("SimHei", 10)
        
        # 播放状态变量
        self.is_playing = False # 播放状态
        self.current_file = None # 当前播放文件
        self.current_position = 0 # 当前播放位置
        self.total_length = 0 # 音视频总时长（秒）
        self.lyrics = [] # 歌词列表
        self.lyrics_index = -1 # 当前歌词索引（从0开始）
        # 播放模式变量
        self.playback_modes = {
            'sequence': {'icon': '➡', 'name': '顺序播放', 'description': '播放完所有歌曲后停止'},
            'single': {'icon': '🔂', 'name': '单曲循环', 'description': '重复播放当前歌曲'},
            'random': {'icon': '🔀', 'name': '随机播放', 'description': '随机播放列表中的歌曲'},
            'loop': {'icon': '🔄', 'name': '列表循环', 'description': '播放完所有歌曲后重新开始'}
        }
        self.current_playback_mode = 'loop' # 默认列表循环
        
        # 音量控制相关变量
        self.last_volume_value = 70  # 初始化音量值为70%
        
        # 视频控制属性
        self.lyrics_timer = None
        
        # 多文件夹支持的数据结构
        self.folders = {}  # 存储所有添加的文件夹及其文件
        self.current_folder = None # 当前播放文件夹
        self.current_folder_songs = [] # 当前播放文件夹下的歌曲列表
        self.current_song_index = -1 # 当前播放歌曲在所处文件夹下的索引（从0开始）
        
        # 配置信息
        self.config = {}  # 用于存储播放器配置，如ffplay路径等
        
        # 搜索功能相关变量
        self.is_search_mode = False  # 是否处于搜索模式
        self.original_songs = []  # 原始歌曲列表备份
        self.search_results = []  # 搜索结果列表
        
        # 进度条相关变量
        self.just_jumped = False  # 标记是否刚刚执行了跳转
        self.jump_position = 0  # 存储跳转的位置
        self.manually_adjusting = False  # 标记是否正在手动调整进度条
        self.playback_start_time = None  # 开始播放的时间戳
        self.last_update_time = None  # 最后一次更新进度的时间戳
        
        # 视频播放相关变量
        self.is_video = False # 当前是否正在播放视频
        self.video_surface = None # 视频表面
        self.video_timer = None # 视频定时器
        self.video_frame_count = 0 # 视频当前帧计数
        self.video_frame_rate = 0 # 视频当前帧率
        self.video_last_frame_time = 0 # 上一帧的时间戳
        
        # 创建主框架初始化pygame
        pygame.init()  # 先完整初始化pygame
        pygame.mixer.init()  # 再初始化pygame mixer
        logger.info(f"pygame初始化状态: {pygame.get_init()}")
        
        # 创建界面
        self._create_ui()
        
        # 加载上次保存的状态
        self._load_state()
        
        # 添加一个小延迟确保窗口位置正确应用
        self.root.after(10, self._ensure_window_position)
        
    def _ensure_window_position(self):
        """确保窗口在屏幕内显示的辅助方法，不再强制设置默认大小"""
        # 获取当前窗口的位置，但使用我们记录的大小值
        x = self.root.winfo_x()
        y = self.root.winfo_y()
        # 使用记录的大小值，而不是直接从窗口获取，确保高度保持不变
        width = self.last_width
        height = self.last_height
        
        # 获取屏幕尺寸
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        # 确保窗口在屏幕内显示（如果部分超出屏幕）
        if x < 0 or y < 0 or x + width > screen_width or y + height > screen_height:
            # 如果窗口完全不可见或大部分超出屏幕，则居中显示
            if x + width < 0 or y + height < 0 or x > screen_width or y > screen_height:
                x = (screen_width - width) // 2
                y = (screen_height - height) // 2
            else:
                # 只调整超出屏幕的部分
                if x < 0: x = 0
                if y < 0: y = 0
                if x + width > screen_width: x = screen_width - width
                if y + height > screen_height: y = screen_height - height
            
            # 调整窗口位置，但保持用户设置的大小
            self.root.geometry(f"{width}x{height}+{x}+{y}")
        
        # 设置窗口在所有窗口上方显示，然后立即取消，确保窗口位置正确应用
        self.root.attributes('-topmost', 1)
        self.root.after(10, lambda: self.root.attributes('-topmost', 0))
        
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self._on_closing)    

    def folder_previous(self):
        """切换到上一个文件夹"""
        children = self.folder_tree.get_children()
        if not children:
            # 显示自动消失的临时提示信息
            self.show_temp_message("文件夹列表为空，请先添加文件或文件夹", "red", "white")
            return
            
        # 获取当前选中的文件夹
        selection = self.folder_tree.selection()
        if selection:
            current_idx = children.index(selection[0])
            # 计算上一个文件夹索引（循环）
            next_idx = (current_idx - 1) % len(children)
        else:
            # 如果没有选中的文件夹，默认选中最后一个
            next_idx = len(children) - 1
            
        # 选中上一个文件夹
        self.folder_tree.selection_set(children[next_idx])
        self.folder_tree.focus(children[next_idx])
        self.folder_tree.see(children[next_idx])
        
        # 更新歌曲列表
        self._update_song_list_for_selected_folder()
            
    def folder_next(self):
        """切换到下一个文件夹"""
        children = self.folder_tree.get_children()
        if not children:
            # 显示自动消失的临时提示信息
            self.show_temp_message("文件夹列表为空，请先添加文件或文件夹", "red", "white")
            return
            
        # 获取当前选中的文件夹
        selection = self.folder_tree.selection()
        if selection:
            current_idx = children.index(selection[0])
            # 计算下一个文件夹索引（循环）
            next_idx = (current_idx + 1) % len(children)
        else:
            # 如果没有选中的文件夹，默认选中第一个
            next_idx = 0
            
        # 选中下一个文件夹
        self.folder_tree.selection_set(children[next_idx])
        self.folder_tree.focus(children[next_idx])
        self.folder_tree.see(children[next_idx])
        
        # 更新歌曲列表
        self._update_song_list_for_selected_folder()
            
    def _update_song_list_for_selected_folder(self):
        """更新选中文件夹的歌曲列表"""
        selection = self.folder_tree.selection()
        if not selection:
            return
            
        try:
            # 获取文件夹路径
            values = self.folder_tree.item(selection[0], "values")
            if values and len(values) > 0:
                folder_path = values[0]
                folder_path = folder_path.replace('/', os.sep).replace('\\', os.sep)
                folder_path = os.path.normpath(folder_path)
                
                # 确保文件夹路径在folders字典中
                if folder_path in self.folders:
                    # 更新当前文件夹和歌曲列表
                    self.current_folder = folder_path
                    self.current_folder_songs = self.folders[folder_path]
                    
                    # 如果当前处于搜索模式，重置搜索模式
                    if self.is_search_mode:
                        self.is_search_mode = False
                        self.search_results = []
                    
                    # 清空并填充歌曲列表（动态补0位数）
                    self.song_listbox.delete(0, tk.END)
                    total_songs = len(self.current_folder_songs)
                    # 计算需要的位数（根据歌曲总数）
                    num_digits = len(str(total_songs)) if total_songs > 0 else 1
                    for i, file_path in enumerate(self.current_folder_songs, 1):
                        self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
        except Exception as e:
            logger.error(f"更新歌曲列表失败: {str(e)}")
    
    def _create_ui(self):
        """创建用户界面"""
        # 创建菜单栏
        menubar = tk.Menu(self.root)
        filemenu = tk.Menu(menubar, tearoff=0)
        filemenu.add_command(label="添加文件", command=self.add_file)
        filemenu.add_command(label="添加文件夹", command=self.add_folder)
        filemenu.add_separator() # 分隔线
        filemenu.add_command(label="恢复默认布局", command=self.restore_default_layout)
        filemenu.add_separator() # 分隔线
        filemenu.add_command(label="退出", command=self._on_closing)
        menubar.add_cascade(label="文件", menu=filemenu)
        
        self.root.config(menu=menubar) # 配置菜单栏
        
        # 创建主框架，分为左右两部分
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 左侧：文件夹和歌曲列表
        left_frame = ttk.LabelFrame(main_frame, text="文件浏览", padding="10")
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5)
        
        # 右侧：播放控制和视频/歌词显示
        right_frame = ttk.Frame(main_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=5, pady=5)
        
        # 保存引用，用于后续调整布局
        self.left_frame = left_frame
        self.right_frame = right_frame
        
        # 初始化左右框架的宽度比例（各占50%）
        self.left_width = None
        self.right_width = None
        # 绑定Configure事件来获取初始宽度
        self._update_initial_widths()
        
        # 记录初始窗口宽度
        self.last_width = self.root.winfo_width()
        logger.info(f"初始窗口宽度：{self.last_width}")
        
        # 绑定窗口大小变化事件，实现智能布局调整
        self.root.bind("<Configure>", self.on_window_resize)
        
        # 文件夹操作按钮
        folder_buttons_frame = ttk.Frame(left_frame)
        folder_buttons_frame.pack(fill=tk.X, pady=(0, 5))
        
        self.add_folder_btn = ttk.Button(folder_buttons_frame, text="添加文件夹", command=self.add_folder)
        self.add_folder_btn.pack(side=tk.LEFT, padx=2)
        
        self.remove_folder_btn = ttk.Button(folder_buttons_frame, text="移除文件夹", command=self.remove_folder)
        self.remove_folder_btn.pack(side=tk.LEFT, padx=2)
        
        # 添加文件夹翻页按钮
        self.folder_prev_btn = ttk.Button(folder_buttons_frame, text="上翻", command=self.folder_previous)
        self.folder_prev_btn.pack(side=tk.LEFT, padx=2)
        
        self.folder_next_btn = ttk.Button(folder_buttons_frame, text="下翻", command=self.folder_next)
        self.folder_next_btn.pack(side=tk.LEFT, padx=2)
        
        # 创建文件夹树状视图 - 添加垂直和横向滚动条支持
        folder_frame = ttk.Frame(left_frame, height=200)
        folder_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=(0, 5))
        folder_frame.grid_propagate(False)  # 防止框架自动调整大小
        folder_frame.grid_rowconfigure(0, weight=1)  # 第一行扩展
        folder_frame.grid_columnconfigure(0, weight=1)  # 第一列扩展
        
        # 创建垂直滚动条
        self.folder_scrollbar_y = ttk.Scrollbar(folder_frame, orient=tk.VERTICAL)
        self.folder_scrollbar_y.grid(row=0, column=1, sticky="ns")
        
        # 创建横向滚动条
        self.folder_scrollbar_x = ttk.Scrollbar(folder_frame, orient=tk.HORIZONTAL)
        self.folder_scrollbar_x.grid(row=1, column=0, sticky="ew")
        
        # 创建文件夹树并关联滚动条
        self.folder_tree = ttk.Treeview(
            folder_frame, 
            columns=("name"), 
            show="tree",
            yscrollcommand=self.folder_scrollbar_y.set,  # 关联垂直滚动条
            xscrollcommand=self.folder_scrollbar_x.set   # 关联横向滚动条
        )
        self.folder_tree.heading("#0", text="文件夹")
        self.folder_tree.column("#0", width=200, minwidth=200)  # 设置最小宽度
        self.folder_tree.grid(row=0, column=0, sticky="nsew")
        self.folder_scrollbar_y.config(command=self.folder_tree.yview)  # 设置垂直滚动条命令
        self.folder_scrollbar_x.config(command=self.folder_tree.xview)  # 设置横向滚动条命令
        
        self.folder_tree.bind("<Double-1>", self.on_folder_double_click) # 双击文件夹
        self.folder_tree.bind("<Button-3>", self.show_folder_context_menu)  # 文件夹右键菜单
        
        # 创建搜索框区域
        search_frame = ttk.Frame(left_frame)
        search_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(10, 5))
        
        # 创建搜索框样式
        self.search_style = ttk.Style()
        self.search_style.configure("Search.TEntry", padding=8, relief="flat")
        
        # 创建搜索框容器
        search_container = ttk.Frame(search_frame, relief="solid", borderwidth=1)
        search_container.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        # 创建搜索图标标签，增大字体大小以提高可见性
        search_icon = ttk.Label(search_container, text="🔍", font=(self.font[0], self.font[1] + 10))
        search_icon.pack(side=tk.LEFT, padx=(5, 0))
        
        # 创建搜索输入框
        self.search_entry = ttk.Entry(search_container, font=self.font, style="Search.TEntry")
        self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 5))
        self.search_entry.insert(0, "搜索歌曲...")
        self.search_entry.config(foreground="gray50")  # 设置占位文本为灰色
        self.search_entry.bind("<FocusIn>", self.on_search_focus_in)
        self.search_entry.bind("<FocusOut>", self.on_search_focus_out)
        self.search_entry.bind("<Return>", self.on_search_enter)
        # 添加文本变化事件绑定
        self.search_entry.bind("<<Modified>>", self.on_search_text_change)
        # 创建StringVar以跟踪文本变化
        self.search_var = tk.StringVar()
        self.search_entry.config(textvariable=self.search_var)
        self.search_var.trace_add("write", self.on_search_text_change_var)
        
        # 创建搜索和清除按钮样式
        self.button_style = ttk.Style()
        self.button_style.configure("SearchButton.TButton", padding=(10, 5), relief="flat")
        
        # 创建播放模式按钮样式，确保图标居中显示
        self.button_style.configure("PlayMode.TButton", padding=(5, 2), width=3, anchor="center")
        
        # 创建搜索按钮和清除按钮
        button_frame = ttk.Frame(search_frame)
        button_frame.pack(side=tk.LEFT, padx=(5, 0))
        
        self.search_btn = ttk.Button(button_frame, text="搜索", command=self.search_songs, width=6, style="SearchButton.TButton")
        self.search_btn.pack(side=tk.LEFT, padx=(0, 3))
        
        self.clear_search_btn = ttk.Button(button_frame, text="清除", command=self.clear_search_results, width=6, style="SearchButton.TButton")
        self.clear_search_btn.pack(side=tk.LEFT, padx=(3, 0))
        self.clear_search_btn.config(state=tk.DISABLED)

        # 创建歌曲列表视图 - 添加垂直和横向滚动条支持
        song_frame = ttk.Frame(left_frame, height=200)
        song_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)

        # 创建垂直滚动条
        self.song_scrollbar_y = ttk.Scrollbar(song_frame, orient=tk.VERTICAL)
        self.song_scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)

        # 创建横向滚动条
        self.song_scrollbar_x = ttk.Scrollbar(song_frame, orient=tk.HORIZONTAL)
        self.song_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

        # 创建歌曲列表并关联滚动条
        self.song_listbox = tk.Listbox(
            song_frame, 
            font=self.font, 
            width=30, 
            height=15,
            yscrollcommand=self.song_scrollbar_y.set,  # 关联垂直滚动条
            xscrollcommand=self.song_scrollbar_x.set   # 关联横向滚动条
        )
        self.song_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.song_scrollbar_y.config(command=self.song_listbox.yview)  # 设置垂直滚动条命令
        self.song_scrollbar_x.config(command=self.song_listbox.xview)  # 设置横向滚动条命令
        
        self.song_listbox.bind("<Double-1>", self.on_song_double_click) # 双击文件
        self.song_listbox.bind("<Button-3>", self.show_song_context_menu)  # 文件右键菜单
        
        # 创建文件夹右键菜单
        self.folder_context_menu = tk.Menu(self.root, tearoff=0)
        self.folder_context_menu.add_command(label="打开", command=self.open_selected_folder)
        self.folder_context_menu.add_command(label="加载全部", command=self.load_all_songs_from_folder)
        self.folder_context_menu.add_command(label="打开文件夹", command=self.open_folder_location)
        self.folder_context_menu.add_command(label="移除", command=self.remove_folder)
        
        # 创建文件右键菜单
        self.song_context_menu = tk.Menu(self.root, tearoff=0)
        self.song_context_menu.add_command(label="播放", command=self.play_selected_song)
        self.song_context_menu.add_command(label="从列表移除", command=self.remove_song_from_list)
        self.song_context_menu.add_command(label="打开文件所在的位置", command=self.open_song_folder)
        self.song_context_menu.add_command(label="打开歌词所在的位置", command=self.open_lyrics_folder)
        
        # 创建播放控制区域
        control_frame = ttk.Frame(right_frame, padding="10")
        control_frame.pack(fill=tk.X)
        
        # 使用Unicode图标作为上一首按钮
        self.prev_btn = ttk.Button(control_frame, text="⏮", command=self.play_previous, style="PlayMode.TButton")
        self.prev_btn.pack(side=tk.LEFT, padx=5)
        # 为上一首按钮添加tooltip
        self.prev_tooltip = Tooltip(self.prev_btn)
        self.prev_tooltip.configure(text="上一首")
        
        # 使用Unicode图标作为播放/暂停按钮
        self.play_icon = "▶"
        self.pause_icon = "⏸"
        self.play_btn = ttk.Button(control_frame, text=self.play_icon, command=self.toggle_play, style="PlayMode.TButton")
        self.play_btn.pack(side=tk.LEFT, padx=5)
        # 为播放按钮添加tooltip
        self.play_tooltip = Tooltip(self.play_btn)
        self.play_tooltip.configure(text="播放")
        
        # 使用Unicode图标作为下一首按钮
        self.next_btn = ttk.Button(control_frame, text="⏭", command=self.play_next, style="PlayMode.TButton")
        self.next_btn.pack(side=tk.LEFT, padx=5)
        # 为下一首按钮添加tooltip
        self.next_tooltip = Tooltip(self.next_btn)
        self.next_tooltip.configure(text="下一首")
        
        # 创建播放模式切换按钮
        self.play_mode_btn = ttk.Button(control_frame, text=self.playback_modes[self.current_playback_mode]['icon'], command=self.toggle_playback_mode, style="PlayMode.TButton")
        self.play_mode_btn.pack(side=tk.LEFT, padx=5)
        # 为播放模式按钮添加tooltip
        self.play_mode_tooltip = Tooltip(self.play_mode_btn)
        self.play_mode_tooltip.configure(text=self.playback_modes[self.current_playback_mode]['name']+":"+self.playback_modes[self.current_playback_mode]['description'])
        
        # 创建打开当前文件所在位置按钮
        self.open_folder_btn = ttk.Button(control_frame, text="📁", command=self.open_current_file_folder, style="PlayMode.TButton")
        self.open_folder_btn.pack(side=tk.LEFT, padx=5)
        # 为打开文件所在位置按钮添加tooltip
        self.open_folder_tooltip = Tooltip(self.open_folder_btn)
        self.open_folder_tooltip.configure(text="打开当前文件所在的位置")
        
        # 创建打开当前歌词所在位置按钮
        self.open_lyrics_folder_btn = ttk.Button(control_frame, text="📝", command=self.open_current_lyrics_folder, style="PlayMode.TButton")
        self.open_lyrics_folder_btn.pack(side=tk.LEFT, padx=5)
        # 为打开当前歌词所在位置按钮添加tooltip
        self.open_lyrics_tooltip = Tooltip(self.open_lyrics_folder_btn)
        self.open_lyrics_tooltip.configure(text="打开当前歌词所在的位置")
        
        # 创建清空当前播放信息按钮
        self.clear_playback_btn = ttk.Button(control_frame, text="🗑️", command=self.clear_playback_info, style="PlayMode.TButton")
        self.clear_playback_btn.pack(side=tk.LEFT, padx=5)
        # 为清空当前播放信息按钮添加tooltip
        self.clear_playback_tooltip = Tooltip(self.clear_playback_btn)
        self.clear_playback_tooltip.configure(text="清空当前播放信息")
        
        # 创建进度条
        self.progress_frame = ttk.Frame(right_frame, padding="5")  # 减少内边距使布局更紧凑
        self.progress_frame.pack(fill=tk.X)  # 移除expand=True以避免过度扩展
        
        ttk.Label(self.progress_frame, text="时长: ").pack(side=tk.LEFT)
        
        self.progress_var = tk.DoubleVar()
        # 创建时间显示标签（当前/总时长）- 移除固定宽度限制，让标签自动调整大小显示完整内容
        self.time_display_label = ttk.Label(self.progress_frame, text="00:00/00:00")
        self.time_display_label.pack(side=tk.RIGHT, padx=5)
        
        # 时长进度条设置为200，考虑到右侧时间显示标签的布局和扩展效果
        self.progress_bar = ttk.Scale(self.progress_frame, orient=tk.HORIZONTAL, length=200,
                                     from_=0, to=100, variable=self.progress_var, command=self.seek)
        self.progress_bar.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        self.progress_bar.config(state=tk.DISABLED)  # 初始设置进度条为不可拖动
        
        # 创建音量控制
        volume_frame = ttk.Frame(right_frame, padding="5")  # 减少内边距使布局更紧凑
        volume_frame.pack(fill=tk.X)
        
        ttk.Label(volume_frame, text="音量: ").pack(side=tk.LEFT)
        
        self.volume_var = tk.DoubleVar(value=70)
        # 初始化音量值记录，用于跟踪音量变化
        self.last_volume_value = 70
        
        # 音量进度条设置为150，考虑到左侧音量显示标签的布局和扩展效果，与时长进度条视觉上保持一致
        self.volume_scale = ttk.Scale(volume_frame, orient=tk.HORIZONTAL, length=150,
                                     from_=0, to=100, variable=self.volume_var, command=self.set_volume)
        self.volume_scale.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        # 创建音量显示标签（当前/最大）
        self.volume_display_label = ttk.Label(volume_frame, text=f"{int(self.volume_var.get())}/100%", width=10)
        self.volume_display_label.pack(side=tk.LEFT, padx=(5, 0))
        
        pygame.mixer.music.set_volume(0.7)
        
        # 跟踪是否正在手动调整进度条，避免自动更新和手动调整冲突
        self.manually_adjusting = False
        
        # 创建当前播放信息区域
        self.info_frame = ttk.Frame(right_frame, padding="10")
        self.info_frame.pack(fill=tk.X)
        
        self.file_label = ttk.Label(self.info_frame, text="未播放任何文件", font=self.font, wraplength=500)
        self.file_label.pack(anchor=tk.W)
        
        # 创建视频/歌词显示区域
        self.media_frame = ttk.LabelFrame(right_frame, text="视频/歌词", padding="10")
        self.media_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建视频显示画布
        self.video_canvas = tk.Canvas(self.media_frame, bg="white", height=300)
        self.video_canvas.pack(fill=tk.BOTH, expand=True)
        
        # 保存画布ID
        self.video_canvas_id = None
        
        # 创建歌词显示组件（默认隐藏）
        # 添加边框样式：relief=solid设置为实线边框，bd=1设置边框宽度为1像素
        self.lyrics_text = scrolledtext.ScrolledText(self.media_frame, font=("SimHei", 12),wrap=tk.WORD, bg="#f0f0f0", height=10, relief=tk.SOLID, bd=1)
        self.lyrics_text.pack_forget()  # 初始隐藏歌词显示
    
    def add_file(self):
        """添加文件"""
        # 使用已定义的扩展名变量构建文件类型过滤选项
        audio_exts_str = ' '.join([f'*{ext}' for ext in audio_extensions])
        video_exts_str = ' '.join([f'*{ext}' for ext in video_extensions])
        media_exts_str = f'{audio_exts_str} {video_exts_str}'
        
        filetypes = [
            ("媒体文件", media_exts_str),
            ("音频文件", audio_exts_str),
            ("视频文件", video_exts_str),
            ("MP3文件", "*.mp3"),
            ("MP4文件", "*.mp4"),
            ("所有文件", "*.*")
        ]
        file_paths = filedialog.askopenfilenames(title="选择媒体文件", filetypes=filetypes)
        if file_paths:
            # 按文件夹组织文件
            folder_files = {}
            first_file = None
            
            for file_path in file_paths:
                # 规范化文件路径，解决Windows系统上的大小写敏感性问题
                file_path = os.path.normpath(file_path)
                
                # 获取文件所在文件夹
                folder_path = os.path.dirname(file_path)
                
                # 如果是第一个文件，记录下来
                if first_file is None:
                    first_file = file_path
                    first_folder = folder_path
                
                # 将文件按文件夹分组
                if folder_path not in folder_files:
                    folder_files[folder_path] = []
                folder_files[folder_path].append(file_path)
                
                # 如果文件夹不存在于folders中，创建它
                if folder_path not in self.folders:
                    self.folders[folder_path] = []
                    
                    # 直接添加到树状视图
                    folder_count = len(self.folder_tree.get_children()) + 1
                    total_folders = folder_count
                    num_digits = len(str(total_folders))
                    folder_name = os.path.basename(folder_path)
                    self.folder_tree.insert("", "end", text=f"{folder_count:0{num_digits}d}. {folder_name}", values=(folder_path,))
                    
                    # 更新所有文件夹的序号
                    children = self.folder_tree.get_children()
                    total_folders = len(children)
                    num_digits = len(str(total_folders)) if total_folders > 0 else 1
                    for idx, child in enumerate(children, 1):
                        current_folder_path = self.folder_tree.item(child, "values")[0]
                        current_folder_name = os.path.basename(current_folder_path)
                        self.folder_tree.item(child, text=f"{idx:0{num_digits}d}. {current_folder_name}")
                    

                
                # 添加文件到对应的文件夹列表
                if file_path not in self.folders[folder_path]:
                    self.folders[folder_path].append(file_path)
            
            # 设置当前文件夹为第一个文件所在的文件夹
            self.current_folder = first_folder
            self.current_folder_songs = self.folders[self.current_folder]
            
            # 清空并重新填充歌曲列表，显示当前文件夹的所有歌曲
            self.song_listbox.delete(0, tk.END)
            # 计算需要的位数（根据歌曲总数）
            total_songs = len(self.current_folder_songs)
            num_digits = len(str(total_songs)) if total_songs > 0 else 1
            for i, song in enumerate(self.current_folder_songs, 1):
                self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(song)}")
            
            # 自动选中第一个添加的文件夹
            for item in self.folder_tree.get_children():
                item_values = self.folder_tree.item(item, "values")
                if item_values and len(item_values) > 0 and item_values[0] == self.current_folder:
                    self.folder_tree.selection_set(item)
                    self.folder_tree.focus(item)
                    self.folder_tree.see(item)
                    break
            
            # 找到第一个新添加文件的索引并选中
            if first_file in self.current_folder_songs:
                new_song_index = self.current_folder_songs.index(first_file)
                self.current_song_index = new_song_index
                self.song_listbox.selection_set(new_song_index)
                self.song_listbox.see(new_song_index)
            self.song_listbox.see(0)  # 滚动到当前歌曲
            
            # 播放第一个选中的文件
            if first_file:
                self.play_file(first_file)
    
    def add_folder(self):
        """添加文件夹"""
        folder_path = filedialog.askdirectory(title="选择媒体文件夹")
        if folder_path:
            # 规范化文件夹路径
            folder_path = os.path.normpath(folder_path)
            
            self.add_folder_to_tree(folder_path)
            
            # 自动展开并选中新添加的文件夹
            for item in self.folder_tree.get_children():
                item_values = self.folder_tree.item(item, "values")
                if item_values and len(item_values) > 0 and item_values[0] == folder_path:
                    self.folder_tree.selection_set(item)
                    self.folder_tree.focus(item)
                    self.folder_tree.see(item)
                    break
            
            # 强制切换到该文件夹（无论是否在folders中）
            if folder_path in self.folders:
                self.current_folder = folder_path
                self.current_folder_songs = self.folders[folder_path]
                
                # 清空并填充歌曲列表（动态补0位数）
                self.song_listbox.delete(0, tk.END)
                total_songs = len(self.current_folder_songs)
                # 计算需要的位数（根据歌曲总数）
                num_digits = len(str(total_songs)) if total_songs > 0 else 1
                for i, file_path in enumerate(self.current_folder_songs, 1):
                    self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
            else:
                # 显示自动消失的临时提示信息
                self.show_temp_message("文件夹添加失败，请重试", "red", "white")
    
    def add_folder_to_tree(self, folder_path):
        """添加文件夹到树状视图"""
        # 规范化文件夹路径，解决Windows系统上的大小写敏感性问题
        folder_path = os.path.normpath(folder_path)
        
        # 检查文件夹是否已添加
        if folder_path in self.folders:
            # 显示自动消失的临时提示信息
            self.show_temp_message(f"文件夹'{os.path.basename(folder_path)}'已添加，可以选中文件夹右击'加载全部'按钮来刷新文件夹内容","red","white",4000)
            return
        
        # 收集文件夹中的媒体文件
        audio_files = []
        for root, _, files in os.walk(folder_path):
            for file in files:
                if any(file.lower().endswith(ext) for ext in media_extensions):
                    audio_files.append(os.path.join(root, file))
        
        if not audio_files:
            # 显示自动消失的临时提示信息
            self.show_temp_message("所选文件夹中没有找到支持的音频文件", "red", "white")
            return
        
        # 保存文件夹及其文件
        self.folders[folder_path] = audio_files
        
        # 添加到树状视图，带序号显示
        folder_count = len(self.folder_tree.get_children()) + 1
        total_folders = folder_count
        # 计算需要的位数（根据文件夹总数）
        num_digits = len(str(total_folders))
        folder_name = os.path.basename(folder_path)
        self.folder_tree.insert("", "end", text=f"{folder_count:0{num_digits}d}. {folder_name}", values=(folder_path,))
        
        # 更新所有文件夹的序号，确保序号对齐
        children = self.folder_tree.get_children()
        total_folders = len(children)
        num_digits = len(str(total_folders)) if total_folders > 0 else 1
        for idx, child in enumerate(children, 1):
            current_folder_path = self.folder_tree.item(child, "values")[0]
            current_folder_name = os.path.basename(current_folder_path)
            self.folder_tree.item(child, text=f"{idx:0{num_digits}d}. {current_folder_name}")
        
        # 如果当前正在查看这个文件夹，刷新歌曲列表（带序号）
        if self.current_folder == folder_path:
            self.current_folder_songs = audio_files
            self.song_listbox.delete(0, tk.END)
            for i, file_path in enumerate(audio_files, 1):
                self.song_listbox.insert(tk.END, f"{i}. {os.path.basename(file_path)}")
    
    def on_folder_double_click(self, event):
        """处理文件夹双击事件"""
        # 获取右键点击的位置
        item = self.folder_tree.identify_row(event.y)
        if item:
            # 确保项目被选中
            self.folder_tree.selection_set(item)
            
            try:
                # 获取文件夹路径 - 检查values是否包含数据
                values = self.folder_tree.item(item, "values")
                if values and len(values) > 0:
                    # 修复Windows系统中路径斜杠问题
                    folder_path = values[0]
                    # 确保路径格式正确并规范化
                    folder_path = folder_path.replace('/', os.sep).replace('\\', os.sep)
                    folder_path = os.path.normpath(folder_path)
                    
                    # 确保文件夹路径在folders字典中
                    if folder_path in self.folders:
                        # 更新当前文件夹和歌曲列表
                        self.current_folder = folder_path
                        self.current_folder_songs = self.folders[folder_path]
                        
                        # 如果当前处于搜索模式，重置搜索模式
                        if self.is_search_mode:
                            self.is_search_mode = False
                            self.search_results = []
                        
                        # 清空并填充歌曲列表（动态补0位数）
                        self.song_listbox.delete(0, tk.END)
                        total_songs = len(self.current_folder_songs)
                        # 计算需要的位数（根据歌曲总数）
                        num_digits = len(str(total_songs)) if total_songs > 0 else 1
                        for i, file_path in enumerate(self.current_folder_songs, 1):
                            self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
                    else:
                        # 显示自动消失的临时提示信息
                        self.show_temp_message("文件夹数据未找到", "red", "white")
                else:
                    # 显示自动消失的临时提示信息
                    self.show_temp_message("无效的文件夹项", "red", "white")
            except Exception as e:
                # 处理所有可能的异常
                # 显示自动消失的临时提示信息
                self.show_temp_message(f"无法打开该文件夹: {str(e)}", "red", "white")
            
    def open_folder_location(self):
        """打开选中文件夹所在的位置"""
        selection = self.folder_tree.selection()
        if selection:
            folder_path = self.folder_tree.item(selection[0], "values")[0]
            try:
                # Windows系统下使用explorer打开文件夹
                if os.name == 'nt':
                    subprocess.Popen(['explorer', folder_path])
                # macOS下使用open打开文件夹
                elif os.name == 'posix' and 'darwin' in platform.system().lower():
                    subprocess.Popen(['open', folder_path])
                # Linux下尝试不同的文件管理器
                elif os.name == 'posix' and 'linux' in platform.system().lower():
                    # 尝试多种常见的Linux文件管理器
                    file_managers = ['nautilus', 'dolphin', 'thunar', 'pcmanfm', 'nemo']
                    for fm in file_managers:
                        try:
                            subprocess.Popen([fm, folder_path])
                            break
                        except FileNotFoundError:
                            continue
            except Exception as e:
                logger.error(f"打开文件夹失败: {str(e)}")
                messagebox.showerror("错误", f"无法打开文件夹: {str(e)}")
    
    def show_folder_context_menu(self, event):
        """显示文件夹右键菜单"""
        # 获取右键点击的位置
        item = self.folder_tree.identify_row(event.y)
        if item:
            # 选中点击的项
            self.folder_tree.selection_set(item)
            # 显示右键菜单
            self.folder_context_menu.post(event.x_root, event.y_root)
            
    def show_song_context_menu(self, event):
        """显示歌曲右键菜单"""
        # 获取右键点击的位置
        index = self.song_listbox.nearest(event.y)
        if 0 <= index < self.song_listbox.size():
            # 选中点击的项
            self.song_listbox.selection_clear(0, tk.END)
            self.song_listbox.selection_set(index)
            # 显示右键菜单
            self.song_context_menu.post(event.x_root, event.y_root)
            
    def open_selected_folder(self):
        """打开选中的文件夹"""
        selection = self.folder_tree.selection()
        if selection:
            folder_path = self.folder_tree.item(selection[0], "values")[0]
            self.add_to_history(folder_path)
            self.current_folder = folder_path
            self.current_folder_songs = self.folders[folder_path]
            self.song_listbox.delete(0, tk.END)
            total_songs = len(self.current_folder_songs)
            # 计算需要的位数（根据歌曲总数）
            num_digits = len(str(total_songs)) if total_songs > 0 else 1
            for i, file_path in enumerate(self.current_folder_songs, 1):
                self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
                
    def load_all_songs_from_folder(self):
        """从选中的文件夹中加载所有歌曲"""
        selection = self.folder_tree.selection()
        if not selection:
            self.show_temp_message("请先选择要加载歌曲的文件夹")
            return
        
        item = selection[0]
        folder_path = self.folder_tree.item(item, "values")[0]
        
        # 规范化文件夹路径
        folder_path = os.path.normpath(folder_path)
        
        try:
            # 重新收集该文件夹中的所有音频文件
            audio_files = []
            for root, _, files in os.walk(folder_path):
                for file in files:
                    if any(file.lower().endswith(ext) for ext in media_extensions):
                        file_path = os.path.join(root, file)
                        audio_files.append(os.path.normpath(file_path))
            
            # 更新文件夹中的文件列表
            self.folders[folder_path] = audio_files
            
            # 如果当前正在查看这个文件夹，刷新歌曲列表
            if self.current_folder == folder_path:
                self.current_folder_songs = audio_files
                self.song_listbox.delete(0, tk.END)
                total_songs = len(self.current_folder_songs)
                # 计算需要的位数（根据歌曲总数）
                num_digits = len(str(total_songs)) if total_songs > 0 else 1
                for i, file_path in enumerate(self.current_folder_songs, 1):
                    self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
            
            # 显示成功提示
            self.show_temp_message(f"成功加载{len(audio_files)}首歌曲", "green", "white")
            
        except Exception as e:
            # 显示错误提示
            self.show_temp_message(f"加载歌曲时出错: {str(e)}", "red", "white")
    
    def remove_folder(self):
        """从播放器中移除文件夹（不实际删除文件）"""
        selection = self.folder_tree.selection()
        if not selection:
            # 显示自动消失的临时提示信息
            self.show_temp_message("请先选择要移除的文件夹", "red", "white")
            return
            
        item = selection[0]
        folder_path = self.folder_tree.item(item, "values")[0]
        
        # 规范化文件夹路径，确保与self.folders中的路径一致
        folder_path = os.path.normpath(folder_path)
        
        # 保存要移除的文件夹名称，避免后续被循环覆盖
        removed_folder_name = os.path.basename(folder_path)
        
        # 无论是否是当前文件夹，都清空歌曲列表
        self.song_listbox.delete(0, tk.END)
        
        # 如果是当前文件夹，更新状态
        if folder_path == self.current_folder:
            self.current_folder = None
            self.current_folder_songs = []
        
        # 如果当前正在播放该文件夹中的歌曲，停止播放
        if self.current_file and os.path.dirname(self.current_file) == folder_path:
            self.stop_playback()
        
        # 从数据结构中移除
        if folder_path in self.folders:
            del self.folders[folder_path]
            
        # 获取移除前的所有子项，找出被移除项的位置
        children_before_remove = self.folder_tree.get_children()
        removed_index = -1
        for i, child in enumerate(children_before_remove):
            if child == item:
                removed_index = i
                break
        
        # 从树状视图中移除
        self.folder_tree.delete(item)
        
        # 更新剩余文件夹的序号
        children = self.folder_tree.get_children()
        total_folders = len(children)
        
        # 计算需要的位数（根据文件夹总数）
        num_digits = len(str(total_folders)) if total_folders > 0 else 1
        for idx, child in enumerate(children, 1):
            # 使用局部变量，避免覆盖原始的folder_path
            current_folder_path = self.folder_tree.item(child, "values")[0]
            current_folder_name = os.path.basename(current_folder_path)
            self.folder_tree.item(child, text=f"{idx:0{num_digits}d}. {current_folder_name}")
        
        # 如果有剩余文件夹，自动选中合适的文件夹
        if children:
            # 确定要选中的项
            # 如果有下一个则选中下一个，如果没有下一个则选中第一个
            if removed_index < len(children):  # 如果移除的不是最后一个元素，则选中下一个
                selected_index = removed_index
            else:  # 如果移除的是最后一个元素，则选中第一个
                selected_index = 0
            
            # 选中相应的文件夹
            selected_item = children[selected_index]
            self.folder_tree.selection_set(selected_item)
            
            # 获取选中文件夹的路径并更新当前文件夹和歌曲列表
            selected_folder_path = self.folder_tree.item(selected_item, "values")[0]
            if selected_folder_path in self.folders:
                # 更新当前文件夹和歌曲列表
                self.current_folder = selected_folder_path
                self.current_folder_songs = self.folders[selected_folder_path]
                
                # 清空并填充歌曲列表（动态补0位数）
                self.song_listbox.delete(0, tk.END)
                total_songs = len(self.current_folder_songs)
                # 计算需要的位数（根据歌曲总数）
                num_digits = len(str(total_songs)) if total_songs > 0 else 1
                for i, file_path in enumerate(self.current_folder_songs, 1):
                    self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
        
        # 检查是否所有文件夹都已被移除
        if not self.folders:
            # 确保进度条完全清空
            self.current_position = 0
            self.total_length = 0
            self.progress_var.set(0)
            self.time_display_label.config(text="00:00/00:00")
        
        # 显示自动消失的临时提示信息
        self.show_temp_message(f"文件夹'{removed_folder_name}'已从播放器中移除", "lightgreen", "black")
        
    def play_selected_song(self):
        """播放选中的歌曲"""
        selection = self.song_listbox.curselection()
        if selection and self.current_folder_songs:
            index = selection[0]
            file_path = self.current_folder_songs[index]
            self.current_song_index = index
            self.play_file(file_path)
            
    def remove_song_from_list(self):
        """从列表移除选中的歌曲"""
        selection = self.song_listbox.curselection()
        if not selection or not self.current_folder_songs:
            return
            
        index = selection[0]
        file_path = self.current_folder_songs[index]
        file_name = os.path.basename(file_path)
        
        # 检查是否正在播放被移除的歌曲
        if self.current_file and self.current_file == file_path:
            self.stop_playback()
        
        # 从数据结构中移除
        self.current_folder_songs.pop(index)
        
        # 更新当前索引
        if self.current_song_index == index:
            self.current_song_index = -1
        elif self.current_song_index > index:
            self.current_song_index -= 1
            
        # 重新加载并重新排序歌曲列表，更新序号
        self.song_listbox.delete(0, tk.END)
        total_songs = len(self.current_folder_songs)
        # 计算需要的位数（根据歌曲总数）
        num_digits = len(str(total_songs)) if total_songs > 0 else 1
        for i, file_path in enumerate(self.current_folder_songs, 1):
            self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
            
        # 显示自动消失的临时提示信息
        self.show_temp_message(f"歌曲 '{file_name}' 已从列表中移除", "lightgreen", "black", 2000, 0.5, 0.95)
                
    def open_song_folder(self):
        """打开选中歌曲所在的文件夹并定位到文件"""
        selection = self.song_listbox.curselection()
        if selection and self.current_folder_songs:
            index = selection[0]
            file_path = self.current_folder_songs[index]
            folder_path = os.path.dirname(file_path)
            try:
                # Windows系统下使用explorer打开文件夹并选中文件
                if os.name == 'nt':
                    # /select参数可以打开文件夹并选中指定文件
                    subprocess.Popen(['explorer', '/select,', file_path])
                # macOS下使用open打开文件夹并选中文件
                elif os.name == 'posix' and 'darwin' in platform.system().lower():
                    # -R参数可以在Finder中显示文件
                    subprocess.Popen(['open', '-R', file_path])
                # Linux下尝试不同的文件管理器
                elif os.name == 'posix' and 'linux' in platform.system().lower():
                    # 尝试多种常见的Linux文件管理器
                    file_managers = ['nautilus', 'nemo', 'caja', 'dolphin', 'thunar']
                    opened = False
                    for fm in file_managers:
                        try:
                            if fm in ['nautilus', 'nemo', 'caja']:
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'dolphin':
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'thunar':
                                subprocess.Popen([fm, '--select', file_path])
                            opened = True
                            break
                        except FileNotFoundError:
                            continue
                    # 如果所有文件管理器都失败，回退到打开文件夹
                    if not opened:
                        subprocess.Popen(['xdg-open', folder_path])
                # 其他系统回退到打开文件夹
                else:
                    subprocess.Popen(['xdg-open', folder_path])
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件夹: {str(e)}")
            
    
    def on_song_double_click(self, event):
        """处理歌曲双击事件"""
        # 支持搜索模式和普通模式
        if hasattr(self, 'is_search_mode') and self.is_search_mode and hasattr(self, 'search_results') and self.search_results:
            # 搜索模式下的处理
            selection = self.song_listbox.curselection()
            if not selection:
                return
            
            index = selection[0]
            if 0 <= index < len(self.search_results):
                song_path, folder_path = self.search_results[index]
                
                # 更新当前索引和文件夹
                self.current_song_index = index
                # 尝试在folders中找到对应的文件夹
                if hasattr(self, 'folders') and folder_path in self.folders:
                    self.current_folder = folder_path
                    self.current_folder_songs = self.folders[folder_path]
                    # 在普通模式的歌曲列表中查找对应的索引
                    try:
                        normal_index = self.current_folder_songs.index(song_path)
                        self.current_song_index = normal_index
                    except ValueError:
                        # 如果找不到，使用0作为默认索引
                        self.current_song_index = 0
                
                # 确保从0开始播放新歌曲
                self.current_position = 0
                self.play_file(song_path)
                
                # 更新歌曲列表选择状态
                self.song_listbox.selection_clear(0, tk.END)
                self.song_listbox.selection_set(index)
                self.song_listbox.see(index)
        else:
            # 普通模式下的处理
            # 获取选中的歌曲索引
            selection = self.song_listbox.curselection()
            if not selection:
                return
            
            index = selection[0]
            if hasattr(self, 'current_folder_songs') and self.current_folder_songs:
                file_path = self.current_folder_songs[index]
                
                # 更新当前索引
                self.current_song_index = index
                
                # 确保从0开始播放新歌曲
                self.current_position = 0
                self.play_file(file_path)
    
    def play_file(self, file_path):
        """播放文件"""
        try:
            # 保存当前歌曲索引，避免stop_playback重置后丢失
            saved_index = self.current_song_index
            
            # 停止当前播放
            self.stop_playback()
            
            # 恢复保存的索引（仅在有效范围内恢复）
            if saved_index >= 0 and self.current_folder_songs and saved_index < len(self.current_folder_songs):
                self.current_song_index = saved_index
                
            # 确保进度条从0开始
            self.current_position = 0
            self.progress_var.set(0)
            
            # 检查文件类型
            _, ext = os.path.splitext(file_path)
            ext = ext.lower()
            
            # 初始化视频相关属性
            if not hasattr(self, 'is_video'):
                self.is_video = False
            if not hasattr(self, 'video_surface'):
                self.video_surface = None
            if not hasattr(self, 'video_rect'):
                self.video_rect = None
            if not hasattr(self, 'video_timer'):
                self.video_timer = None
            
            self.is_video = ext in video_extensions
            self.current_file = file_path
            
            # 更新文件信息
            file_name = os.path.basename(file_path)
            self.file_label.config(text=file_name)
            
            # 设置进度条为可拖动
            self.progress_bar.config(state=tk.NORMAL)
            
            # 保持进度条始终为0-100的百分比模式
            self.progress_bar.configure(to=100)
            
            if self.is_video:
                # 视频文件处理
                try:
                    # 使用OpenCV加载视频
                    import cv2
                    self.cap = cv2.VideoCapture(file_path)
                    if not self.cap.isOpened():
                        raise Exception("无法打开视频文件")
                    
                    # 获取视频信息
                    self.fps = self.cap.get(cv2.CAP_PROP_FPS)
                    self.frame_count = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
                    self.total_length = self.frame_count / self.fps if self.fps > 0 else 0
                    
                    # 更新时间显示标签
                    self.time_display_label.config(text=f"00:00/{self.format_time(self.total_length)}")
                    
                    # 更新状态
                    self.is_playing = True
                    self.play_btn.config(text=self.pause_icon)
                    self.playback_start_time = time.time()
                    self.current_position = 0
                    
                    # 显示视频区域，隐藏歌词
                    self.show_video()
                    
                    # 初始化视频音频（自动处理，不需要开关）
                    try:
                        # 添加更多调试信息
                        logger.info(f"尝试加载视频音频文件: {file_path}")
                        
                        # 检查pygame初始化状态
                        if pygame.mixer.get_init() is None:
                            logger.warning("警告: pygame.mixer尚未初始化")
                            # 尝试使用更具体的参数初始化
                            pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)
                            logger.info(f"已重新初始化pygame.mixer: {pygame.mixer.get_init()}")
                        else:
                            logger.info(f"pygame.mixer当前初始化状态: {pygame.mixer.get_init()}")
                        
                        # 检查音频设备
                        logger.info(f"pygame音频设备是否忙碌: {pygame.mixer.music.get_busy()}")
                        
                        # 清除任何可能存在的音频数据
                        pygame.mixer.music.stop()
                        
                        # 尝试加载视频的音频部分
                        pygame.mixer.music.load(file_path)
                        logger.info("视频音频加载成功")
                        
                        # 设置音量并验证
                        pygame.mixer.music.set_volume(1.0)  # 最大音量
                        logger.info(f"设置音量为1.0")
                        logger.info(f"当前音量: {pygame.mixer.music.get_volume()}")
                        
                        # 开始播放音频
                        pygame.mixer.music.play(start=0)
                        logger.info("视频音频开始播放")
                        
                        # 验证音频是否真正开始播放
                        pygame.time.wait(100)  # 等待一小段时间
                        logger.info(f"音频播放状态检查: {pygame.mixer.music.get_busy()}")
                    except Exception as audio_e:
                        logger.error(f"尝试加载视频音频时出错: {str(audio_e)}")
                        # 专门处理ModPlug_Load失败的情况
                        if "ModPlug_Load" in str(audio_e):
                            logger.error("ModPlug_Load失败，但会继续播放视频")
                        # 尝试其他方式加载音频（使用pygame.Sound）
                        try:
                            logger.info("尝试使用pygame.Sound加载视频音频")
                            video_sound = pygame.mixer.Sound(file_path)
                            video_sound.play()
                            video_sound.set_volume(1.0)
                            logger.info("使用pygame.Sound加载并播放成功")
                        except Exception as sound_e:
                            logger.error(f"使用pygame.Sound加载也失败: {str(sound_e)}")
                            
                            # 尝试使用外部ffplay工具播放视频音频
                            try:
                                logger.info("尝试使用外部ffplay工具播放视频音频")
                                self._play_video_audio_externally()
                                logger.info("外部ffplay工具调用完成")
                            except Exception as ff_e:
                                logger.error(f"外部ffplay播放失败: {str(ff_e)}")
                        
                    # 启动视频更新计时器（无论音频是否加载成功）
                    self.update_video()
                    
                except Exception as e:
                    # 如果OpenCV加载失败，尝试使用pygame作为备选
                    try:
                        # 简单的视频占位符处理
                        self.total_length = 60  # 默认60秒（实际应用中应该从文件获取）
                        self.time_display_label.config(text=f"00:00/{self.format_time(self.total_length)}")
                        self.is_playing = True
                        self.play_btn.config(text=self.pause_icon)
                        self.playback_start_time = time.time()
                        self.current_position = 0
                        self.show_video()
                        
                        # 创建一个简单的视频帧作为示例
                        width, height = 320, 240
                        self.video_surface = pygame.Surface((width, height))
                        self.video_surface.fill((0, 0, 255))  # 蓝色背景
                        font = pygame.font.SysFont(None, 36)
                        text = font.render("视频播放中", True, (255, 255, 255))
                        text_rect = text.get_rect(center=(width//2, height//2))
                        self.video_surface.blit(text, text_rect)
                        
                        # 启动视频更新计时器
                        self.update_video()
                    except Exception as inner_e:
                        messagebox.showerror("视频播放错误", f"无法播放视频文件: {str(inner_e)}")
                        self.stop_playback()
                        return
            else:
                # 音频文件处理
                pygame.mixer.music.load(file_path)
                
                # 获取并显示文件长度
                if file_path.lower().endswith('.mp3'):
                    audio = MP3(file_path)
                    length = audio.info.length
                else:
                    # 对于其他格式，使用pygame获取长度
                    # 避免影响当前音量状态，使用更安全的方式获取长度
                    try:
                        # 先保存当前音量设置
                        current_volume = pygame.mixer.music.get_volume()
                        
                        # 使用临时方式获取音频长度
                        pygame.mixer.music.set_volume(0)  # 静音
                        pygame.mixer.music.play()
                        pygame.mixer.music.pause()
                        length = pygame.mixer.Sound(file_path).get_length()
                        
                        # 立即恢复原始音量设置
                        pygame.mixer.music.set_volume(current_volume)
                        logger.info(f"获取音频长度后恢复音量: {current_volume}")
                    except Exception as e:
                        logger.error(f"获取音频长度出错: {str(e)}")
                        # 如果出错，使用默认值
                        length = 0
                
                self.total_length = length
                # 更新时间显示标签
                self.time_display_label.config(text=f"00:00/{self.format_time(length)}")
                
                # 显示总歌曲时长和音量数值
                logger.info(f"总歌曲时长: {self.format_time(self.total_length)}")
                logger.info(f"当前音量: {self.volume_var.get()}")
                
                # 更新状态
                self.is_playing = True
                
                # 如果有保存的进度，则从保存的位置开始播放
                if self.current_position > 0 and self.current_position < length:
                    pygame.mixer.music.play(start=self.current_position)
                    self.playback_start_time = time.time() - self.current_position  # 设置播放开始时间戳
                    logger.info(f"从进度 {self.current_position:.2f} 秒开始播放")
                else:
                    pygame.mixer.music.play()
                    self.current_position = 0
                    self.playback_start_time = time.time()  # 设置播放开始时间戳
                    
                self.play_btn.config(text=self.pause_icon)
                
                # 显示歌词区域
                self.show_lyrics()
                
                # 解析歌词
                self.parse_lyrics(file_path)
            
            # 更新进度条
            self.update_progress()
            
        except Exception as e:
            messagebox.showerror("错误", f"播放文件时出错: {str(e)}")
            self.stop_playback()
    
    def toggle_playback_mode(self):
        """切换播放模式：顺序播放 -> 单曲循环 -> 随机播放 -> 列表循环 -> 顺序播放"""
        # 播放模式顺序
        mode_order = ['sequence', 'single', 'random', 'loop']
        
        # 获取当前模式在顺序中的索引
        current_index = mode_order.index(self.current_playback_mode)
        
        # 计算下一个模式的索引
        next_index = (current_index + 1) % len(mode_order)
        
        # 设置下一个播放模式
        self.current_playback_mode = mode_order[next_index]
        
        # 更新按钮显示和tooltip
        self.play_mode_btn.config(text=self.playback_modes[self.current_playback_mode]['icon'])
        self.play_mode_tooltip.configure(text=self.playback_modes[self.current_playback_mode]['name']+":"+self.playback_modes[self.current_playback_mode]['description'])
        
        logger.info(f"已切换到{self.playback_modes[self.current_playback_mode]['name']}")
        
        # 保存状态
        self._save_state()
        
    def open_current_file_folder(self):
        """打开当前正在播放的文件所在的位置"""
        if hasattr(self, 'current_file') and self.current_file:
            file_path = self.current_file
            try:
                # Windows系统下使用explorer打开文件夹并选中文件
                if os.name == 'nt':
                    # /select参数可以打开文件夹并选中指定文件
                    subprocess.Popen(['explorer', '/select,', file_path])
                # macOS下使用open打开文件夹并选中文件
                elif os.name == 'posix' and 'darwin' in platform.system().lower():
                    # -R参数可以在Finder中显示文件
                    subprocess.Popen(['open', '-R', file_path])
                # Linux下尝试不同的文件管理器
                elif os.name == 'posix' and 'linux' in platform.system().lower():
                    # 尝试多种常见的Linux文件管理器
                    file_managers = ['nautilus', 'nemo', 'caja', 'dolphin', 'thunar']
                    opened = False
                    for fm in file_managers:
                        try:
                            if fm in ['nautilus', 'nemo', 'caja']:
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'dolphin':
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'thunar':
                                subprocess.Popen([fm, '--select', file_path])
                            opened = True
                            break
                        except FileNotFoundError:
                            continue
                    # 如果所有文件管理器都失败，回退到打开文件夹
                    if not opened:
                        folder_path = os.path.dirname(file_path)
                        subprocess.Popen(['xdg-open', folder_path])
                # 其他系统回退到打开文件夹
                else:
                    folder_path = os.path.dirname(file_path)
                    subprocess.Popen(['xdg-open', folder_path])
            except Exception as e:
                logger.error(f"无法打开文件所在的位置: {str(e)}")
                # 显示自动消失的临时提示信息
                self.show_temp_message(f"无法打开文件所在的位置: {str(e)}", "red", "white")
        else:
            # 显示自动消失的临时提示信息
            self.show_temp_message("没有正在播放的文件", "red", "white")
    
    def open_current_lyrics_folder(self):
        """打开当前正在播放的文件的歌词所在的位置并定位到歌词文件"""
        if hasattr(self, 'current_file') and self.current_file:
            self._open_lyrics_for_song(self.current_file)
        else:
            # 显示自动消失的临时提示信息
            self.show_temp_message("没有正在播放的文件", "red", "white")
            
    def clear_playback_info(self):
        """清空当前播放信息，同时清空播放状态"""
        self.stop_playback()
        
        # 显示自动消失的临时提示信息
        self.show_temp_message("当前播放信息已清空", "lightgreen", "black")
            
    def toggle_play(self):
        """切换播放状态"""
        logger.info(f"进入toggle_play方法，当前is_playing状态: {self.is_playing}")
        
        if not self.current_file:
            # 确保在没有文件时显示正确的播放按钮状态
            self.play_btn.config(text=self.play_icon)
            self.is_playing = False

            # 显示自动消失的临时提示信息
            self.show_temp_message("没有当前要播放的文件", "red", "white")

            logger.info("没有当前文件，设置播放按钮为'播放'图标")
            return
        
        if hasattr(self, 'is_video') and self.is_video:
            # 视频播放控制
            if self.is_playing:
                # 暂停播放
                self.is_playing = False
                self.play_btn.config(text=self.play_icon)
                self.play_tooltip.configure(text="播放")
                logger.info("执行视频暂停操作，设置is_playing为False")
                
                # 暂停pygame音频播放
                pygame.mixer.music.pause()
                logger.info("暂停视频音频播放")
                
                # 暂停外部ffplay进程
                if hasattr(self, 'external_audio_process') and self.external_audio_process is not None:
                    try:
                        logger.info("尝试暂停外部ffplay进程")
                        # 在Windows上向ffplay进程发送空格键（模拟暂停）
                        # 使用SendKeys或pywinauto向窗口发送按键
                        # 由于我们使用了-nodisp选项，ffplay没有窗口，所以这里我们需要终止进程
                        # 注意：真正的暂停功能需要ffplay有窗口或者使用其他控制方式
                        # 这里简化处理，直接终止进程，下次播放时从暂停位置重新开始
                        self.external_audio_process.terminate()
                        logger.info("外部ffplay进程已终止（模拟暂停）")
                        self.external_audio_process = None
                    except Exception as e:
                        logger.error(f"暂停外部ffplay进程时出错: {str(e)}")
                
                # 记录暂停时的位置
                if self.playback_start_time is not None:
                    paused_position = time.time() - self.playback_start_time
                    self.current_position = paused_position
                    self.playback_start_time = None  # 暂停时清除播放开始时间
            else:
                # 继续播放视频
                self.is_playing = True
                self.play_btn.config(text=self.pause_icon)
                self.play_tooltip.configure(text="暂停")
                logger.info("执行视频继续播放操作，设置is_playing为True")
                # 重新计算播放开始时间
                if self.playback_start_time is None:
                    self.playback_start_time = time.time() - self.current_position
                # 启动视频更新计时器
                self.update_video()
                
                # 播放视频的音频部分
                if self.current_file:
                    try:
                        # 先停止任何可能的音频播放
                        pygame.mixer.music.stop()
                        
                        # 确保之前的外部进程已终止
                        if hasattr(self, 'external_audio_process') and self.external_audio_process is not None:
                            try:
                                self.external_audio_process.terminate()
                                self.external_audio_process = None
                            except:
                                pass
                        
                        logger.info("尝试使用pygame播放视频音频")
                        
                        # 重新初始化pygame.mixer以确保正确的状态
                        pygame.mixer.quit()
                        pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=1024)
                        
                        # 尝试加载并播放视频文件的音频部分
                        try:
                            pygame.mixer.music.load(self.current_file)
                            pygame.mixer.music.set_volume(1.0)  # 设置最大音量
                            pygame.mixer.music.play(start=self.current_position if self.current_position > 0 else 0)
                            
                            # 验证音频是否真正开始播放
                            pygame.time.wait(200)
                            if pygame.mixer.music.get_busy():
                                logger.info(f"pygame音频播放成功，从 {self.current_position:.2f} 秒开始播放")
                                logger.info(f"pygame音频播放状态: {pygame.mixer.music.get_busy()}")
                            else:
                                logger.warning("pygame音频已尝试播放，但状态显示未播放")
                                
                                # 先尝试使用pygame.Sound作为备选
                                try:
                                    logger.info("尝试使用pygame.Sound播放视频音频")
                                    pygame.mixer.music.stop()
                                    sound = pygame.mixer.Sound(self.current_file)
                                    sound.play()
                                    sound.set_volume(1.0)
                                    logger.info("使用pygame.Sound播放成功")
                                except Exception as sound_e:
                                    logger.error(f"pygame.Sound播放失败: {str(sound_e)}")
                                    
                                    # 如果pygame.Sound也无法播放，再尝试使用外部ffplay工具
                                    try:
                                        logger.info("尝试使用外部ffplay工具播放视频音频")
                                        self._play_video_audio_externally()
                                    except Exception as ff_e:
                                        logger.error(f"外部ffplay播放失败: {str(ff_e)}")
                                        # 更友好的错误提示，明确告知用户问题和解决方案
                                        messagebox.showinfo("音频播放", "视频音频播放遇到问题。\n\n原因分析:\n1. pygame内置音频引擎不支持此视频格式\n2. 系统上未找到ffmpeg(包含ffplay)外部播放器\n\n解决方案:\n1. 安装ffmpeg(包含ffplay)并添加到系统PATH中\n   - 下载地址: https://ffmpeg.org/download.html\n   - 安装完成后需要重启计算机使PATH生效\n2. 使用专门的视频播放器(如VLC、PotPlayer等)播放此视频文件\n3. 尝试将视频文件转换为更兼容的格式(如MP3、OGG等)")
                        except Exception as pygame_e:
                            logger.error(f"pygame音频加载失败: {str(pygame_e)}")
                            
                            # 先尝试使用pygame.Sound作为备选
                            try:
                                logger.info("尝试使用pygame.Sound播放视频音频")
                                sound = pygame.mixer.Sound(self.current_file)
                                sound.play()
                                sound.set_volume(1.0)
                                logger.info("使用pygame.Sound播放成功")
                            except Exception as sound_e:
                                logger.error(f"pygame.Sound播放失败: {str(sound_e)}")
                                
                                # 如果pygame.Sound也无法播放，再尝试使用外部ffplay工具
                                try:
                                    logger.info("尝试使用外部ffplay工具播放视频音频")
                                    self._play_video_audio_externally()
                                    logger.info("外部ffplay工具调用完成")
                                except Exception as ff_e:
                                    logger.error(f"外部ffplay播放失败: {str(ff_e)}")
                                    # 更友好的错误提示，明确告知用户问题和解决方案
                                    messagebox.showinfo("音频播放", "视频音频播放遇到问题。\n\n原因分析:\n1. pygame内置音频引擎不支持此视频格式\n2. 外部ffplay播放器无法正常工作\n\n解决方案:\n1. 确认已正确安装ffmpeg(包含ffplay)并添加到系统PATH中\n   - 下载地址: https://ffmpeg.org/download.html\n   - 安装完成后需要重启计算机使PATH生效\n2. 打开命令提示符，输入'ffplay -version'验证安装是否成功\n3. 确保视频文件路径正确且文件没有损坏\n4. 使用专门的视频播放器(如VLC、PotPlayer等)播放此视频文件")
                    except Exception as e:
                        logger.error(f"视频音频播放过程中发生总体错误: {str(e)}")
                        # 显示自动消失的临时提示信息
                        self.show_temp_message("视频音频播放遇到问题，请尝试其他视频文件", "red", "white")
        else:
            # 音频播放控制
            if self.is_playing:
                # 暂停播放
                pygame.mixer.music.pause()
                self.play_btn.config(text=self.play_icon)
                self.is_playing = False
                self.play_tooltip.configure(text="播放")
                logger.info("执行暂停操作，设置is_playing为False")
                # 记录暂停时的位置，用于恢复播放
                if self.playback_start_time is not None:
                    paused_position = time.time() - self.playback_start_time
                    self.current_position = paused_position
                    self.playback_start_time = None  # 暂停时清除播放开始时间
            else:
                # 检查文件是否仍然存在
                if not os.path.exists(self.current_file):
                    # 显示自动消失的临时提示信息
                    self.show_temp_message("文件不存在，无法播放", "red", "white")
                    # 
                    self.stop_playback()
                    logger.error("文件不存在，执行停止播放操作")
                    return
                    
                try:
                    # 无论音乐是否处于busy状态，直接尝试恢复播放
                    if pygame.mixer.music.get_busy():
                        # 尝试恢复播放
                        pygame.mixer.music.unpause()
                        logger.info("执行恢复播放操作")
                    else:
                        # 如果无法恢复播放，则重新加载文件
                        logger.info("音乐未处于播放中，重新加载文件")
                        pygame.mixer.music.load(self.current_file)
                        # 从保存的位置开始播放
                        if self.current_position > 0:
                            pygame.mixer.music.play(start=self.current_position)
                            logger.info(f"从进度 {self.current_position:.2f} 秒开始播放")
                        else:
                            pygame.mixer.music.play()
                            logger.info("从头开始播放")
                    
                    # 重新计算播放开始时间
                    if self.current_position > 0:
                        self.playback_start_time = time.time() - self.current_position
                    else:
                        self.playback_start_time = time.time()
                    
                    # 验证播放是否真正开始
                    pygame.time.wait(100)
                    if not pygame.mixer.music.get_busy():
                        logger.warning("音频未能正常播放，状态显示为未播放")
                        # 尝试使用pygame.mixer.Sound作为备选
                        try:
                            logger.info("尝试使用pygame.Sound作为备选播放方式")
                            pygame.mixer.music.stop()
                            sound = pygame.mixer.Sound(self.current_file)
                            sound.play()
                            sound.set_volume(1.0)
                            logger.info("使用pygame.Sound播放成功")
                        except Exception as sound_e:
                            logger.error(f"使用pygame.Sound播放也失败: {str(sound_e)}")
                            # 继续原有流程，让用户知道有这个问题
                except Exception as e:
                    error_msg = str(e)
                    logger.error(f"播放时出错: {error_msg}")
                    # 专门处理ModPlug_Load失败的情况
                    if "ModPlug_Load" in error_msg:
                        messagebox.showerror("音频解码错误", f"无法解码文件的音频部分: {error_msg}\n\n这通常发生在尝试播放某些不支持格式的视频文件时。\n\n解决方案:\n1. 尝试安装ffmpeg（包含ffplay工具）以启用外部音频播放\n2. 使用专门的视频播放器播放此视频文件\n3. 尝试转换视频格式为更兼容的格式")
                    else:
                        messagebox.showerror("错误", f"播放文件时出错: {error_msg}")
                    self.stop_playback()
                    return
                    
                self.play_btn.config(text=self.pause_icon)
                self.play_tooltip.configure(text="暂停")
                self.is_playing = True
                logger.info("设置播放状态为播放中")
        
        logger.info(f"toggle_play方法结束，最终is_playing状态: {self.is_playing}")
        self.update_progress()
    
    def _terminate_external_process(self):
        """终止外部ffplay进程的辅助方法（优化版本）"""
        if hasattr(self, 'external_audio_process') and self.external_audio_process is not None:
            try:
                logger.info("尝试终止外部ffplay进程")
                
                # 先尝试使用更温和的方法终止进程
                try:
                    # 向进程发送停止信号
                    self.external_audio_process.terminate()
                    
                    # 使用单独的线程等待进程终止，避免主线程阻塞
                    import threading
                    import queue
                    
                    # 创建一个队列来接收结果
                    result_queue = queue.Queue()
                    
                    def wait_for_process(process, timeout=1.0):
                        try:
                            process.wait(timeout=timeout)
                            result_queue.put(True)
                        except subprocess.TimeoutExpired:
                            result_queue.put(False)
                    
                    # 启动等待线程
                    wait_thread = threading.Thread(target=wait_for_process, args=(self.external_audio_process, 0.5))
                    wait_thread.daemon = True  # 设置为守护线程，避免阻塞程序退出
                    wait_thread.start()
                    
                    # 等待线程完成或超时（0.5秒）
                    wait_thread.join(timeout=0.5)
                    
                    # 检查结果
                    try:
                        terminated = result_queue.get(block=False)
                        if terminated:
                            logger.info("外部ffplay进程已优雅终止")
                        else:
                            logger.warning("ffplay进程终止超时，强制终止")
                            if platform.system() == "Windows":
                                # 在Windows上使用taskkill强制终止
                                subprocess.call(['taskkill', '/F', '/PID', str(self.external_audio_process.pid)], 
                                                shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                            else:
                                # 在Unix/Linux/macOS上使用kill强制终止
                                os.kill(self.external_audio_process.pid, signal.SIGKILL)
                            logger.info("外部ffplay进程已强制终止")
                    except queue.Empty:
                        logger.warning("等待进程终止的线程未完成")
                        # 即使线程未完成，也继续清理
                except Exception as term_e:
                    logger.error(f"终止进程时出错: {str(term_e)}")
                    # 继续尝试清理引用
                
                # 无论如何都要清理进程引用
                self.external_audio_process = None
                logger.info("外部进程引用已清理")
            except Exception as e:
                logger.error(f"终止外部ffplay进程时出错: {str(e)}")
        else:
            logger.info("没有正在运行的外部ffplay进程需要终止")
    
    def stop_playback(self):
        """停止播放"""
        # 停止音频播放
        pygame.mixer.music.stop()
        
        # 终止外部ffplay进程
        self._terminate_external_process()
        
        self.is_playing = False # 重置播放状态为False
        self.is_video = False   # 重置视频播放状态
        self.current_folder = None  # 清空当前播放文件夹信息
        self.current_file = None  # 清空当前播放的文件信息
        self.current_position = 0
        self.current_song_index = -1  # 重置当前歌曲索引
        self.total_length = 0  # 重置总时长
        self.playback_start_time = None  # 停止播放时清除播放开始时间
        self.last_update_time = None  # 停止播放时清除最后更新时间
        self.progress_var.set(0)
        self.progress_bar.config(state=tk.DISABLED)  # 设置进度条为不可拖动
        self.play_btn.config(text=self.play_icon)
        self.file_label.config(text="未播放任何文件")  # 更新文件标签显示
        self.time_display_label.config(text="00:00/00:00")  # 重置时间显示标签
        
        # 清除视频相关资源
        if hasattr(self, 'cap') and self.cap is not None:
            self.cap.release()
            self.cap = None
        if hasattr(self, 'video_surface') and self.video_surface:
            self.video_surface = None
        if hasattr(self, 'video_timer') and self.video_timer:
            try:
                self.root.after_cancel(self.video_timer)
            except:
                pass  # 忽略取消不存在的计时器的错误
            self.video_timer = None
        if hasattr(self, 'video_canvas_id') and self.video_canvas_id:
            try:
                self.video_canvas.delete(self.video_canvas_id)
            except:
                pass  # 忽略删除不存在的画布元素的错误
            self.video_canvas_id = None
        self.show_lyrics()  # 停止播放时显示歌词区域
        
        # 清除歌词显示和数据
        if self.lyrics_timer:
            self.root.after_cancel(self.lyrics_timer)
        self.lyrics = []  # 清空歌词列表
        self.lyrics_index = -1
        self.update_lyrics_display()
    
    def play_previous(self):
        """播放上一首"""
        if self.current_folder_songs and len(self.current_folder_songs) > 0:
            # 根据不同的播放模式处理上一首逻辑
            if self.current_playback_mode == 'single':
                # 单曲循环：保持当前索引不变
                pass
            elif self.current_playback_mode == 'random':
                # 随机播放：随机选择上一首（实际上是随机选择，而不是真正的上一首）
                if len(self.current_folder_songs) > 1:
                    # 确保不重复播放同一首歌
                    new_index = self.current_song_index
                    while new_index == self.current_song_index:
                        new_index = random.randint(0, len(self.current_folder_songs) - 1)
                    self.current_song_index = new_index
            elif self.current_playback_mode == 'sequence':
                # 顺序播放：如果不是第一首，则播放上一首；否则停止
                if self.current_song_index > 0:
                    self.current_song_index -= 1
                else:
                    # 已经是第一首，停止播放
                    self.stop_playback()
                    return
            else:  # loop模式（默认）
                # 列表循环：播放完第一首后跳到最后一首
                if self.current_song_index > 0:
                    self.current_song_index -= 1
                else:
                    self.current_song_index = len(self.current_folder_songs) - 1
            
            # 切换歌曲时重置播放进度
            self.current_position = 0
            self.play_file(self.current_folder_songs[self.current_song_index])
            
            # 定位到歌曲列表中的当前歌曲
            self.song_listbox.selection_clear(0, tk.END)
            self.song_listbox.selection_set(self.current_song_index)
            self.song_listbox.see(self.current_song_index)
        else:
            # 显示自动消失的临时提示信息
            self.show_temp_message("没有要播放的文件", "red", "white")

    
    def play_next(self):
        """播放下一首"""
        if self.current_folder_songs and len(self.current_folder_songs) > 0:
            # 根据不同的播放模式处理下一首逻辑
            if self.current_playback_mode == 'single':
                # 单曲循环：保持当前索引不变
                pass
            elif self.current_playback_mode == 'random':
                # 随机播放：随机选择下一首
                if len(self.current_folder_songs) > 1:
                    # 确保不重复播放同一首歌
                    new_index = self.current_song_index
                    while new_index == self.current_song_index:
                        new_index = random.randint(0, len(self.current_folder_songs) - 1)
                    self.current_song_index = new_index
            elif self.current_playback_mode == 'sequence':
                # 顺序播放：如果不是最后一首，则播放下一首；否则停止
                if self.current_song_index < len(self.current_folder_songs) - 1:
                    self.current_song_index += 1
                else:
                    # 已经是最后一首，停止播放
                    self.stop_playback()
                    return
            else:  # loop模式（默认）
                # 列表循环：播放完所有歌曲后重新开始
                self.current_song_index = (self.current_song_index + 1) % len(self.current_folder_songs)
            
            # 切换歌曲时重置播放进度
            self.current_position = 0
            self.play_file(self.current_folder_songs[self.current_song_index])
            
            # 定位到歌曲列表中的当前歌曲
            self.song_listbox.selection_clear(0, tk.END)
            self.song_listbox.selection_set(self.current_song_index)
            self.song_listbox.see(self.current_song_index)
        else:
            # 显示自动消失的临时提示信息
            self.show_temp_message("没有要播放的文件", "red", "white")


    def seek(self, value):
        """跳转播放位置"""
        try:
            if not self.current_file or not hasattr(self, 'total_length') or self.total_length <= 0:
                logger.error(f"无法跳转: 当前文件={self.current_file}, 总长度={getattr(self, 'total_length', None)}")
                return
            
            # 标记为正在手动调整
            self.manually_adjusting = True
            
            # 确保value是一个有效的浮点数
            try:
                slider_value = float(value)
            except ValueError:
                logger.info(f"无效的滑块值: {value}")
                self.manually_adjusting = False
                return
            
            # 将滑块值(0-100)转换为实际时间位置
            position = slider_value * self.total_length / 100
            
            # 确保位置不超过文件总长度
            position = max(0, min(position, self.total_length))
            
            # 实时更新时间显示标签
            if hasattr(self, 'total_length'):
                current_time_str = self.format_time(position)
                total_time_str = self.format_time(self.total_length)
                self.time_display_label.config(text=f"{current_time_str}/{total_time_str}")
            
            logger.info(f"尝试跳转到 {position:.2f} 秒 (滑块值: {slider_value})")
            
            # 立即更新当前位置和进度条显示
            self.current_position = position
            self.jump_position = position  # 存储跳转位置
            self.just_jumped = True  # 标记刚刚执行了跳转
            self.playback_start_time = time.time() - position  # 更新播放开始时间戳
            self.last_update_time = time.time()  # 更新最后更新时间
            self.progress_var.set(slider_value)  # 确保滑块位置正确
            
            # 根据文件类型使用不同的跳转方法
            if self.is_video:
                try:
                    # 检查是否正在使用外部ffplay进程播放视频
                    if hasattr(self, 'external_audio_process') and self.external_audio_process is not None and \
                       self.external_audio_process.poll() is None:
                        # 防抖优化: 清除之前的定时器，避免频繁重启
                        if hasattr(self, 'seek_timer'):
                            self.root.after_cancel(self.seek_timer)
                            logger.info("取消之前的跳转定时器")
                        
                        # 设置新的定时器，只有在用户停止拖动200ms后才执行实际的跳转
                        # 这可以避免在快速拖动时频繁重启ffplay进程，提高流畅度
                        self.seek_timer = self.root.after(200, self._perform_delayed_seek, position)
                        logger.info(f"设置跳转定时器，将在200ms后跳转到 {position:.2f} 秒")
                    else:
                        # 对于视频文件，使用OpenCV的跳转方法
                        if hasattr(self, 'cap') and self.cap is not None:
                            # 计算目标帧
                            target_frame = int(position * self.fps)
                            # 跳转到指定帧
                            self.cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
                            logger.info(f"视频跳转到帧 {target_frame}")
                except Exception as e:
                    logger.error(f"视频跳转失败: {str(e)}")
            else:
                try:
                    # 对于音频文件，使用pygame的跳转方法
                    # 先停止当前播放
                    pygame.mixer.music.stop()
                    
                    # 重新加载文件并从指定位置播放
                    pygame.mixer.music.load(self.current_file)
                    pygame.mixer.music.play(start=position)
                    
                    # 确保播放状态正确
                    self.is_playing = True
                    self.play_btn.config(text=self.pause_icon)
                    
                    logger.info(f"音频成功跳转到 {position:.2f} 秒")
                except Exception as e:
                    logger.error(f"音频跳转失败: {str(e)}")
                    # 恢复播放状态
                    if self.is_playing:
                        pygame.mixer.music.play()
                    self.just_jumped = False
            
            logger.info(f"设置just_jumped标志为True")
            
            # 1000毫秒后重置跳转标志
            self.root.after(1000, lambda: setattr(self, 'just_jumped', False))
            
            # 延迟重置手动调整标志，增加延迟时间确保用户操作完成
            self.root.after(1000, lambda: setattr(self, 'manually_adjusting', False))
        except Exception as e:
            logger.error(f"跳转进度时出错: {str(e)}")
            self.manually_adjusting = False
            self.just_jumped = False
    
    def set_volume(self, value):
        """设置音量"""
        try:
            # 记录传入的音量值
            logger.info(f"进入set_volume方法，传入的音量值: {value}")
            
            # 将音量值转换为浮点数
            volume = float(value) / 100
            # 确保音量值在有效范围内
            if 0 <= volume <= 1:
                # 总是设置pygame的音量（无论是否正在使用）
                pygame.mixer.music.set_volume(volume)
                logger.info(f"设置pygame音量为: {volume:.2f}")
                
                # 保存当前音量值到current_volume
                self.current_volume = int(float(value))
                logger.info(f"更新current_volume为: {self.current_volume}%")
                
                # 更新音量显示标签
                self.volume_display_label.config(text=f"{int(float(value))}/100%")
                logger.info(f"更新音量显示标签: {int(float(value))}/100%")
                
                # 对于ffplay播放的视频
                if hasattr(self, 'external_audio_process') and self.external_audio_process is not None and \
                   self.external_audio_process.poll() is None and self.is_video:
                    logger.info("检测到ffplay进程正在运行且播放的是视频文件")
                    try:
                        # 获取当前音量值的整数表示(0-100)
                        current_volume_int = int(float(value))
                        logger.info(f"当前音量整数表示: {current_volume_int}%")
                        
                        # 确保last_volume_value已初始化
                        if not hasattr(self, 'last_volume_value'):
                            logger.info("初始化last_volume_value")
                            self.last_volume_value = current_volume_int
                        
                        # 获取上次记录的音量值
                        last_volume = self.last_volume_value
                        logger.info(f"上次记录的音量值: {last_volume}%")
                        
                        # 计算音量变化方向
                        volume_diff = current_volume_int - last_volume
                        logger.info(f"音量变化差值: {volume_diff}%")
                        
                        # 当音量发生变化且正在播放时
                        if volume_diff != 0:
                            logger.info(f"音量发生变化({last_volume}% -> {current_volume_int}%)，准备调整ffplay音量")
                            # 创建一个单独的线程来处理音量调整，避免阻塞主线程
                            def adjust_volume_thread():
                                try:
                                    # 保存当前播放位置
                                    saved_position = self.current_position
                                    logger.info(f"保存当前播放位置: {saved_position:.2f}秒")
                                    
                                    # 先终止当前ffplay进程
                                    logger.info("开始终止当前ffplay进程")
                                    self._terminate_external_process()
                                    logger.info("ffplay进程已终止")
                                    
                                    # 使用新的音量重新启动ffplay进程
                                    logger.info(f"使用新音量 {current_volume_int}% 重启ffplay进程")
                                    self.current_position = saved_position
                                    self._play_video_audio_externally()
                                    logger.info(f"ffplay进程已重启，音量已调整为{current_volume_int}%")
                                    
                                    logger.info(f"已成功重启ffplay进程调整音量，从{last_volume}%调整到{current_volume_int}%")
                                except Exception as thread_e:
                                    logger.error(f"重启ffplay进程调整音量失败: {str(thread_e)}")
                            
                            # 启动音量调整线程
                            volume_thread = threading.Thread(target=adjust_volume_thread)
                            volume_thread.daemon = True  # 设置为守护线程，避免阻塞程序退出
                            volume_thread.start()
                            logger.info("音量调整线程已启动")
                        else:
                            logger.info(f"音量未变化，保持在{current_volume_int}%")
                        
                        # 记录当前音量值
                        self.last_volume_value = current_volume_int
                        logger.info(f"更新last_volume_value为: {self.last_volume_value}%")
                    except Exception as vol_e:
                        logger.warning(f"动态调整ffplay音量失败: {str(vol_e)}")
                        logger.info(f"音量设置将在下次播放时应用")
                else:
                    if not hasattr(self, 'external_audio_process') or self.external_audio_process is None:
                        logger.info("未找到ffplay进程或进程已终止")
                    elif not self.is_video:
                        logger.info("当前播放的不是视频文件，不需要调整ffplay音量")
            else:
                logger.warning(f"音量值{value}超出有效范围(0-100)，已忽略")
        except Exception as e:
            logger.error(f"设置音量时出错: {str(e)}")
        except Exception as e:
            logger.error(f"设置音量时出错: {str(e)}")
    
    def update_progress(self):
        """更新播放进度"""
        # 确保在没有播放任何歌曲或所有文件夹都被移除时，进度显示为00:00
        if not self.current_file or not self.folders:
            self.time_display_label.config(text="00:00/00:00")
            self.progress_var.set(0)
        else:
            # 获取当前时间
            current_time = time.time()
            
            # 如果刚刚执行了跳转
            if self.just_jumped:
                # 保持使用跳转时设置的位置
                self.current_position = self.jump_position
            else:
                # 只有在播放状态下才更新位置
                if self.is_playing:
                    # 检查是否正在使用外部ffplay进程播放视频
                    if self.is_video and hasattr(self, 'external_audio_process') and \
                       self.external_audio_process is not None and self.external_audio_process.poll() is None:
                        # 对于ffplay播放的视频，使用sync_start_time计算当前位置
                        if hasattr(self, 'sync_start_time') and self.sync_start_time is not None:
                            self.current_position = current_time - self.sync_start_time
                            # 确保不超过总长度
                            if hasattr(self, 'total_length'):
                                self.current_position = min(self.current_position, self.total_length)
                    else:
                        # 基于时间戳计算当前位置，不再使用pygame.get_pos()
                        if self.playback_start_time is not None:
                            self.current_position = current_time - self.playback_start_time
                            # 确保不超过总长度
                            self.current_position = min(self.current_position, self.total_length)
                        else:
                            # 如果playback_start_time未设置，尝试使用pygame的位置作为后备
                            pygame_pos = pygame.mixer.music.get_pos() / 1000  # 转换为秒
                            if pygame_pos >= 0:
                                self.current_position = pygame_pos
                                self.playback_start_time = current_time - self.current_position
            
            # 只有在没有手动调整时才更新进度条显示
            if not self.manually_adjusting and hasattr(self, 'total_length') and self.total_length > 0:
                # 将时间位置转换为百分比(0-100)显示在滑块上
                progress_percent = (self.current_position / self.total_length) * 100
                self.progress_var.set(progress_percent)
                
                # 不再在UI更新循环中频繁记录音量和时长信息，以防止日志文件迅速膨胀
            
            # 更新时间显示标签
            if hasattr(self, 'total_length'):
                current_time_str = self.format_time(self.current_position)
                total_time_str = self.format_time(self.total_length)
                self.time_display_label.config(text=f"{current_time_str}/{total_time_str}")
            
            # 更新歌词显示
            self.update_lyrics_position()
            
            # 检查是否播放完毕
            if (not pygame.mixer.music.get_busy() and 
                self.is_playing and 
                self.current_position > 0 and 
                hasattr(self, 'total_length') and 
                self.current_position >= self.total_length - 0.5):
                self.play_next()
                return
            
            # 更新最后更新时间
            self.last_update_time = current_time
        
        # 继续更新进度条
        self.lyrics_timer = self.root.after(100, self.update_progress)
    
    def parse_lyrics(self, file_path):
        """解析歌词文件"""
        self.lyrics = []
        
        logger.info(f"尝试解析文件歌词: {file_path}")
        
        # 尝试解析LRC文件 - 在歌曲所在目录下的lyrics文件夹中查找
        song_dir = os.path.dirname(file_path)
        file_name = os.path.basename(file_path)
        base_name = os.path.splitext(file_name)[0]
        lrc_folder = os.path.join(song_dir, "lyrics")
        lrc_path = os.path.join(lrc_folder, base_name + '.lrc')

        # 
        song_name = None # 歌曲名
        artist_name = None # 歌手名

        if os.path.exists(lrc_path):
            logger.info(f"找到LRC文件: {lrc_path}")
            self._parse_lrc_file(lrc_path)
        elif file_path.lower().endswith('.mp3'):
            # 尝试从MP3文件的ID3标签中提取歌词
            try:
                logger.info("尝试从ID3标签中提取歌词")
                audio = ID3(file_path)
                
                # 打印所有ID3标签，方便调试
                logger.info("ID3标签内容:")
                for key in audio:
                    logger.info(f"  {key}")
                

                # 尝试提取歌曲名
                # 常见的标题标签
                title_tags = ['TIT2', 'TIT1', 'TIT3']
                for tag in title_tags:
                    if tag in audio:
                        try:
                            title = str(audio[tag])
                            if title.strip():
                                song_name = title.strip()
                                logger.info(f"找到歌曲名: {song_name}")
                                break
                        except Exception as e:
                            logger.warning(f"解析歌曲名标签 {tag} 出错: {e}")
                
                # 尝试提取歌手名
                # 常见的艺术家标签
                artist_tags = ['TPE1', 'TPE2', 'TCOM']
                for tag in artist_tags:
                    if tag in audio:
                        try:
                            artist = str(audio[tag])
                            if artist.strip():
                                artist_name = artist.strip()
                                logger.info(f"找到歌手: {artist_name}")
                                break
                        except Exception as e:
                            logger.warning(f"解析艺术家标签 {tag} 出错: {e}")
                
                # 如果没有找到ID3标签中的歌曲名，尝试从文件名提取
                if not song_name:
                    song_name = base_name
                    logger.info(f"从文件名提取歌曲名: {song_name}")
                    
                    # 尝试从文件名分割歌手名和歌曲名
                    # 常见的分隔符
                    separators = [' - ', '——', '、', '·', '_', ' ', '-']
                    for sep in separators:
                        if sep in base_name:
                            parts = base_name.split(sep, 1)
                            song_name = parts[0].strip()
                            artist_name = parts[1].strip()
                            logger.info(f"从文件名分割: 歌手={artist_name}, 歌曲={song_name}")
                            break
                



                # 尝试不同格式的歌词标签
                lyric_found = False
                
                # 1. 尝试USLT帧(同步歌词/文本)
                for key in audio:
                    if key.startswith('USLT::'):
                        try:
                            lyrics_text = audio[key].text
                            logger.info(f"找到USLT标签: {key}")
                            
                            # 尝试处理编码问题
                            processed_text = lyrics_text
                            if isinstance(processed_text, bytes):
                                # 尝试多种编码方式
                                encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1']
                                for encoding in encodings:
                                    try:
                                        processed_text = lyrics_text.decode(encoding)
                                        logger.info(f"  使用{encoding}编码成功解码USLT标签")
                                        break
                                    except:
                                        continue
                            
                            # 打印部分歌词内容用于调试
                            preview_text = str(processed_text)[:100] + '...' if len(str(processed_text)) > 100 else str(processed_text)
                            logger.info(f"  歌词预览: {preview_text}")
                            self._parse_lrc_text(processed_text)
                            lyric_found = True
                            break
                        except Exception as e:
                            logger.error(f"解析USLT标签出错: {e}")
                
                # 2. 如果没找到，尝试COMM帧(注释)
                if not lyric_found:
                    for key in audio:
                        if key.startswith('COMM::'):
                            try:
                                logger.info(f"尝试从COMM标签提取歌词: {key}")
                                # 处理COMM标签的不同格式
                                if isinstance(audio[key].text, list):
                                    lyrics_text = audio[key].text[0]
                                else:
                                    lyrics_text = audio[key].text
                                
                                # 增强的编码处理逻辑
                                processed_text = lyrics_text
                                
                                # 检查是否为字节类型
                                if isinstance(processed_text, bytes):
                                    # 使用chardet自动检测编码
                                    try:
                                        detection = chardet.detect(lyrics_text)
                                        detected_encoding = detection['encoding']
                                        confidence = detection['confidence']
                                        logger.info(f"  chardet检测到的编码: {detected_encoding}, 可信度: {confidence:.2f}")
                                         
                                        if detected_encoding:
                                            try:
                                                processed_text = lyrics_text.decode(detected_encoding)
                                                logger.info(f"  使用chardet检测的{detected_encoding}编码成功解码COMM标签")
                                            except:
                                                logger.warning(f"  使用chardet检测的{detected_encoding}编码解码失败")
                                                # 回退到尝试多种编码方式
                                                encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1', 'cp936', 'utf-16', 'utf-16le', 'utf-16be']
                                                for encoding in encodings:
                                                    try:
                                                        processed_text = lyrics_text.decode(encoding)
                                                        logger.info(f"  使用{encoding}编码成功解码COMM标签(字节类型)")
                                                        break
                                                    except:
                                                        continue
                                    except Exception as e:
                                        logger.error(f"  chardet检测编码失败: {e}")
                                        # 回退到尝试多种编码方式
                                        encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1', 'cp936', 'utf-16', 'utf-16le', 'utf-16be']
                                        for encoding in encodings:
                                            try:
                                                processed_text = lyrics_text.decode(encoding)
                                                logger.info(f"  使用{encoding}编码成功解码COMM标签(字节类型)")
                                                break
                                            except:
                                                continue
                                # 如果不是字节类型但可能是乱码字符串
                                elif isinstance(processed_text, str):
                                    # 尝试检测并修复可能的编码问题
                                    original_text = processed_text
                                    
                                    # 先尝试直接判断是否为乱码（基于特殊字符模式）
                                    if any(0x4e00 <= ord(c) <= 0x9fff for c in original_text):
                                        # 如果已包含中文字符，可能已经是正确编码
                                        processed_text = original_text
                                        logger.info("  检测到中文字符，可能已经是正确编码")
                                    else:
                                        # 尝试多种编码转换策略
                                        logger.info("  未检测到中文字符，尝试多种编码转换策略")
                                        
                                        # 先尝试将字符串转换为字节，再用chardet检测
                                        try:
                                            # 先尝试用utf-8将字符串转换为字节
                                            temp_bytes = original_text.encode('utf-8', errors='ignore')
                                            detection = chardet.detect(temp_bytes)
                                            detected_encoding = detection['encoding']
                                            confidence = detection['confidence']
                                            logger.info(f"  chardet检测到的编码: {detected_encoding}, 可信度: {confidence:.2f}")
                                             
                                            if detected_encoding:
                                                try:
                                                    processed_text = temp_bytes.decode(detected_encoding)
                                                    if any(0x4e00 <= ord(c) <= 0x9fff for c in processed_text):
                                                        logger.info(f"  使用chardet检测的{detected_encoding}编码成功修复COMM标签乱码")
                                                    else:
                                                        processed_text = original_text
                                                except:
                                                    logger.warning(f"  使用chardet检测的{detected_encoding}编码解码失败")
                                        except Exception as e:
                                            logger.error(f"  chardet检测编码失败: {e}")
                                        
                                        # 如果chardet失败，尝试针对常见乱码模式的特定转换
                                        if not any(0x4e00 <= ord(c) <= 0x9fff for c in processed_text):
                                            # 策略2: 针对常见乱码模式的特定转换
                                            specific_patterns = [
                                                ('utf-8', 'gbk'),  # UTF-8被错误解码为GBK
                                                ('gbk', 'utf-8'),  # GBK被错误解码为UTF-8
                                                ('utf-8', 'latin-1'),  # UTF-8被错误解码为Latin-1
                                                ('gbk', 'latin-1')   # GBK被错误解码为Latin-1
                                            ]
                                            
                                            fixed = False
                                            # 先尝试特定模式转换
                                            for enc1, enc2 in specific_patterns:
                                                try:
                                                    # 尝试将乱码字符串按enc1重新编码
                                                    temp_bytes = original_text.encode(enc1, errors='ignore')
                                                    # 再尝试用enc2解码
                                                    fixed_text = temp_bytes.decode(enc2)
                                                    # 检查是否包含中文字符
                                                    if any(0x4e00 <= ord(c) <= 0x9fff for c in fixed_text):
                                                        processed_text = fixed_text
                                                        logger.info(f"  通过特定模式转换({enc1}->{enc2})成功修复COMM标签乱码")
                                                        fixed = True
                                                        break
                                                except:
                                                    continue
                                
                                # 打印部分歌词内容用于调试
                                preview_text = str(processed_text)[:100] + '...' if len(str(processed_text)) > 100 else str(processed_text)
                                logger.info(f"  COMM标签内容预览: {preview_text}")
                                self._parse_lrc_text(processed_text)
                                if self.lyrics:
                                    lyric_found = True
                                    break
                                else:
                                    # 如果标准解析失败，尝试直接将COMM内容作为歌词行
                                    logger.info("  标准解析失败，尝试直接将COMM内容作为歌词行")
                                    for line in str(processed_text).split('\n'):
                                        line = line.strip()
                                        if line:
                                            self.lyrics.append((0, line))
                                    if self.lyrics:
                                        lyric_found = True
                                        break
                            except Exception as e:
                                logger.error(f"解析COMM标签出错: {e}")
                
                # 3. 如果还是没找到，尝试PRIV帧(私有标签)
                if not lyric_found:
                    for key in audio:
                        if key.startswith('PRIV::'):
                            try:
                                logger.info(f"尝试从PRIV标签提取歌词: {key}")
                                # PRIV标签通常是二进制数据，但我们可以尝试转换为文本
                                priv_data = audio[key].data
                                if isinstance(priv_data, bytes):
                                    try:
                                        lyrics_text = priv_data.decode('utf-8', errors='ignore')
                                        self._parse_lrc_text(lyrics_text)
                                        if self.lyrics:
                                            lyric_found = True
                                            break
                                    except:
                                        try:
                                            lyrics_text = priv_data.decode('gbk', errors='ignore')
                                            self._parse_lrc_text(lyrics_text)
                                            if self.lyrics:
                                                lyric_found = True
                                                break
                                        except:
                                            pass
                            except Exception as e:
                                logger.error(f"解析PRIV标签出错: {e}")
                
                if not lyric_found:
                    logger.info("未在MP3文件中找到歌词")
                    
            except ID3NoHeaderError:
                logger.info("MP3文件没有ID3标签")

                song_name = base_name
                logger.info(f"从文件名提取歌曲名: {song_name}")
                
                # 尝试从文件名分割歌手名和歌曲名
                # 常见的分隔符
                separators = [' - ', '——', '、', '·', '_', ' ', '-']
                for sep in separators:
                    if sep in base_name:
                        parts = base_name.split(sep, 1)
                        song_name = parts[0].strip()
                        artist_name = parts[1].strip()
                        logger.info(f"从文件名分割: 歌手={artist_name}, 歌曲={song_name}")
                        break

            except Exception as e:
                logger.error(f"解析MP3歌词时出错: {str(e)}")
        
        logger.info(f"解析到的歌词行数: {len(self.lyrics)}")
        # 打印前几行歌词用于调试
        if self.lyrics:
            logger.info(f"前3行解析的歌词: {self.lyrics[:3]}")
        
        # 添加歌词行数判断：从MP3文件提取的歌词如果少于10行，不算找到歌词
        # 检查是否是从MP3文件提取的歌词（而不是从LRC文件读取的）
        if file_path.lower().endswith('.mp3') and not os.path.exists(lrc_path):
            if len(self.lyrics) < 5:
                logger.info(f"从MP3提取的歌词行数({len(self.lyrics)})少于5行，不算找到歌词")
                self.lyrics = []
                
                # 如果没找到LRC文件且从MP3标签中提取的歌词行数少于10行，尝试在线抓取歌词
                try:
                    logger.info(f"尝试在线抓取歌词: {file_path}")
                    
                    # 创建歌词抓取器实例
                    fetcher = LyricsFetcher()
                    
                    # 尝试抓取歌词
                    song_info, lyrics = fetcher.fetch_lyrics_by_name(song_name, artist_name)
                    
                    if song_info and lyrics.get("lrc"):
                        logger.info(f"成功抓取歌词：{song_info['song_name']} - {song_info['artist']}（来源：{song_info['source']}）")
                        
                        # 创建歌曲文件目录下的lyrics文件夹
                        song_dir = os.path.dirname(file_path)
                        lyrics_folder = os.path.join(song_dir, "lyrics")
                        if not os.path.exists(lyrics_folder):
                            os.makedirs(lyrics_folder)
                            logger.info(f"创建歌词文件夹：{lyrics_folder}")
                        
                        # 生成与歌曲文件名一致的LRC文件名
                        base_file_name = os.path.basename(file_path)
                        lrc_file_name = os.path.splitext(base_file_name)[0] + '.lrc'
                        lrc_save_path = os.path.join(lyrics_folder, lrc_file_name)
                        
                        # 保存歌词到文件
                        with open(lrc_save_path, "w", encoding="utf-8") as f:
                            f.write(lyrics["lrc"])
                            # 如果有翻译歌词，添加到文件末尾
                            if lyrics.get("translated") and lyrics["translated"].strip():
                                f.write("\n\n[翻译歌词]\n")
                                f.write(lyrics["translated"])
                        
                        logger.info(f"歌词已保存到：{lrc_save_path}")
                        
                        # 解析刚保存的LRC文件
                        self._parse_lrc_file(lrc_save_path)
                    else:
                        logger.info("在线抓取歌词失败")
                except Exception as e:
                    logger.error(f"在线抓取歌词时出错: {str(e)}")
        
        # 更新歌词显示
        self.update_lyrics_display()
    
    def _parse_lrc_file(self, lrc_path):
        """解析歌词文件"""
        try:
            with open(lrc_path, 'r', encoding='utf-8') as f:
                self._parse_lrc_text(f.read())
        except UnicodeDecodeError:
            try:
                with open(lrc_path, 'r', encoding='gbk') as f:
                    self._parse_lrc_text(f.read())
            except Exception:
                pass
    
    def _parse_lrc_text(self, text):
        """解析歌词文本"""

        # 确保文本是字符串
        if isinstance(text, list):
            text = '\n'.join(text)
        
        # 支持多种LRC格式的正则表达式
        # [mm:ss.xx]歌词
        pattern1 = r'\[(\d+):(\d+\.\d+)\](.*)'
        # [mm:ss]歌词
        pattern2 = r'\[(\d+):(\d+)\](.*)'
        # 有些歌词可能使用不同的分隔符，比如[mm-ss.xx]或[mm:ss:xx]
        pattern3 = r'\[(\d+)-(\d+\.\d+)\](.*)'
        pattern4 = r'\[(\d+):(\d+):(\d+\.\d+)\](.*)'
        pattern5 = r'\[(\d+):(\d+):(\d+)\](.*)'
        
        # 尝试使用多种模式
        for line in text.split('\n'):
            line = line.strip()
            if not line:
                continue
            
            # 尝试模式1: [mm:ss.xx]歌词
            match = re.match(pattern1, line)
            if match:
                try:
                    minutes = int(match.group(1))
                    seconds = float(match.group(2))
                    lyric = match.group(3).strip()
                    if lyric:
                        self.lyrics.append((minutes * 60 + seconds, lyric))
                    continue
                except:
                    pass
            
            # 尝试模式2: [mm:ss]歌词
            match = re.match(pattern2, line)
            if match:
                try:
                    minutes = int(match.group(1))
                    seconds = int(match.group(2))
                    lyric = match.group(3).strip()
                    if lyric:
                        self.lyrics.append((minutes * 60 + seconds, lyric))
                    continue
                except:
                    pass
            
            # 尝试模式3: [mm-ss.xx]歌词
            match = re.match(pattern3, line)
            if match:
                try:
                    minutes = int(match.group(1))
                    seconds = float(match.group(2))
                    lyric = match.group(3).strip()
                    if lyric:
                        self.lyrics.append((minutes * 60 + seconds, lyric))
                    continue
                except:
                    pass
            
            # 尝试模式4: [mm:ss:xx.xx]歌词
            match = re.match(pattern4, line)
            if match:
                try:
                    minutes = int(match.group(1))
                    seconds = int(match.group(2)) + float(match.group(3)) / 100
                    lyric = match.group(4).strip()
                    if lyric:
                        self.lyrics.append((minutes * 60 + seconds, lyric))
                    continue
                except:
                    pass
            
            # 尝试模式5: [mm:ss:xx]歌词
            match = re.match(pattern5, line)
            if match:
                try:
                    minutes = int(match.group(1))
                    seconds = int(match.group(2)) + int(match.group(3)) / 100
                    lyric = match.group(4).strip()
                    if lyric:
                        self.lyrics.append((minutes * 60 + seconds, lyric))
                    continue
                except:
                    pass
        
        # 按时间排序歌词
        self.lyrics.sort(key=lambda x: x[0])
        
        # 如果没有找到歌词但文本不为空，尝试直接显示文本（可能是非标准格式的歌词）
        if not self.lyrics and text.strip():
            logger.info("未找到标准格式的歌词，尝试直接显示文本")
            # 尝试清理可能的特殊字符
            cleaned_text = text.replace('\r', '').strip()
            # 如果文本很长，可能是被压缩或编码了的歌词
            if len(cleaned_text) > 1000:
                logger.info("  发现长文本，可能是特殊编码或压缩的歌词数据")
            # 将每行作为一个歌词项显示
            for line in cleaned_text.split('\n'):
                line = line.strip()
                if line:
                    # 过滤掉只包含时间戳但没有歌词的行
                    if not re.match(r'^\[\d+:\d+(\.\d+)?\]$', line):
                        self.lyrics.append((0, line))
    
    def update_lyrics_position(self):
        """更新歌词位置"""
        if not self.lyrics:
            return
        
        # 找到当前应该显示的歌词索引
        new_index = -1
        for i, (time, _) in enumerate(self.lyrics):
            if time <= self.current_position:
                new_index = i
            else:
                break
        
        # 如果索引变化了，更新显示
        if new_index != self.lyrics_index:
            self.lyrics_index = new_index
            self.update_lyrics_display()
    
    def update_lyrics_display(self):
        """更新歌词显示"""
        self.lyrics_text.config(state=tk.NORMAL)
        self.lyrics_text.delete(1.0, tk.END)
        
        if not self.lyrics:
            self.lyrics_text.insert(tk.END, "无歌词")
        else:
            # 显示所有歌词，高亮当前歌词
            for i, (_, lyric) in enumerate(self.lyrics):
                if i == self.lyrics_index:
                    self.lyrics_text.insert(tk.END, lyric + "\n", "current_lyric")
                else:
                    self.lyrics_text.insert(tk.END, lyric + "\n")
            
            # 滚动到当前歌词
            if self.lyrics_index >= 0:
                self.lyrics_text.see(float(self.lyrics_index + 1))
        
        # 设置当前歌词的样式
        self.lyrics_text.tag_config("current_lyric", foreground="blue", font=("SimHei", 12, "bold"))
        self.lyrics_text.config(state=tk.DISABLED)
    
    def format_time(self, seconds):
        """格式化时间为分钟:秒数"""
        minutes, seconds = divmod(int(seconds), 60)
        return f"{minutes:02d}:{seconds:02d}"  # 分钟和秒数都按照两位显示，不足补0
    
    def _update_initial_widths(self):
        """更新初始左右框架宽度 - 优先从内存配置数据加载"""
        # 先尝试从内存配置数据加载宽度值
        try:
            if self.config_data and 'left_width' in self.config_data and 'right_width' in self.config_data:
                saved_left = self.config_data['left_width']
                saved_right = self.config_data['right_width']
                
                # 验证值是否有效
                if isinstance(saved_left, (int, float)) and isinstance(saved_right, (int, float)):
                    logger.info(f"在_update_initial_widths中找到内存配置的宽度值: 左侧 {saved_left}px, 右侧 {saved_right}px")
                    # 使用after来确保窗口已经渲染完成后应用配置值
                    self.root.after(100, lambda: self._apply_saved_widths(saved_left, saved_right))
                    return
        except Exception as e:
            logger.error(f"从内存配置加载宽度值时出错: {str(e)}")
        
        # 如果无法从配置数据加载，使用默认的50%分配
        self.root.after(100, self._set_initial_widths)
    
    def _apply_saved_widths(self, left_width, right_width):
        """应用从配置文件加载的宽度值"""
        # 获取当前窗口宽度
        current_width = self.root.winfo_width()
        current_height = self.root.winfo_height()
        
        # 应用最小宽度限制
        self.left_width = max(100, int(left_width))
        self.right_width = max(100, int(right_width))
        
        # 计算所需的窗口宽度（左右宽度之和 + 边距）
        required_width = self.left_width + self.right_width + 15  # 加上边距
        
        # 如果当前窗口宽度小于所需宽度，调整窗口宽度
        if current_width < required_width:
            x = self.root.winfo_x()
            y = self.root.winfo_y()
            self.root.geometry(f"{required_width}x{current_height}+{x}+{y}")
        
        # 设置框架宽度
        self.left_frame.config(width=self.left_width)
        self.right_frame.config(width=self.right_width)
        
        # 应用框架位置和大小
        available_height = current_height - 10
        self.left_frame.place_configure(x=5, y=5, width=self.left_width, height=available_height)
        self.right_frame.place_configure(x=self.left_width + 10, y=5, width=self.right_width, height=available_height)
        
        logger.info(f"从配置文件应用宽度值: 左侧 {self.left_width}px, 右侧 {self.right_width}px")
    
    def _set_initial_widths(self):
        """设置默认初始宽度"""
        # 获取主窗口宽度
        root_width = self.root.winfo_width()
        # 减去边距和填充
        available_width = root_width - 40  # 考虑窗口边距和框架边距
        
        # 各占50%宽度
        half_width = available_width // 2
        self.left_width = half_width
        self.right_width = half_width
        
        # 设置初始宽度
        self.left_frame.config(width=half_width)
        self.right_frame.config(width=half_width)
    
    def on_window_resize(self, event):
        """窗口大小改变时的处理 - 实现从左侧拖拽时固定右侧布局大小，从右侧拖拽时固定左侧布局大小的功能"""
        # 获取当前窗口宽度和高度
        current_width = self.root.winfo_width()
        current_height = self.root.winfo_height()
        
        # 检测窗口位置变化，判断是从左侧还是右侧拖拽窗口
        x = self.root.winfo_x()
        y = self.root.winfo_y()
        
        # 如果是第一次记录，保存位置信息
        if not hasattr(self, 'last_x'):
            self.last_x = x
            self.last_width = current_width
            self.last_height = current_height
            logger.info(f"初始窗口宽度：{self.last_width}")
            return
        
        # 计算宽度变化和位置变化
        width_diff = current_width - self.last_width
        x_diff = x - self.last_x
        
        # 确保初始宽度已经设置
        if self.left_width is None or self.right_width is None:
            self._set_initial_widths()
        
        # 判断拖拽方向并调整相应框架宽度
        # 从左侧拖拽（窗口左边界移动）
        is_left_drag = width_diff != 0 and x_diff != 0 and abs(x_diff) > abs(width_diff) * 0.3
        
        if is_left_drag:
            # 从左侧拖拽时，固定右侧布局大小
            logger.info(f"从左侧拖拽：x_diff={x_diff}, width_diff={width_diff}")
            
            # 保存当前右侧宽度
            original_right_width = self.right_width
            
            # 计算新的左侧宽度（当前左侧宽度减去窗口左边界的移动量）
            new_left_width = self.left_width - x_diff
            
            # 确保左侧宽度不会变得过小
            if new_left_width > 100:  # 最小宽度限制
                self.left_width = new_left_width
            else:
                self.left_width = 100
                logger.warning(f"左侧宽度过小，保持最小宽度：{self.left_width}px")
                
            # 严格固定右侧布局大小，使其保持不变
            self.right_width = original_right_width  # 强制设置为原始值
            logger.info(f"从左侧拖拽时，右侧宽度严格固定：{self.right_width}px")
            
            # 计算所需的窗口宽度（左右布局宽度之和 + 边距）
            required_width = self.left_width + self.right_width + 15  # 加上边距
            
            # 从左侧拖拽时，窗口位置调整逻辑 - 固定窗口右边界位置
            # 新的窗口x坐标 = 原窗口右边界位置 - 新的窗口总宽度
            # 这样可以确保窗口右边界保持不变，从而右侧布局宽度真正固定
            original_right_edge = self.last_x + self.last_width
            new_window_x = original_right_edge - required_width
            
            # 强制设置窗口宽度和位置
            self.root.geometry(f"{required_width}x{current_height}+{new_window_x}+{y}")
            logger.info(f"强制设置窗口宽度：{required_width}，左侧：{self.left_width}，右侧：{self.right_width}，位置：{new_window_x}")
            
            # 更新当前宽度
            current_width = required_width
            
            # 关键修复：立即更新last_x和last_width，确保下一次窗口调整时使用最新的位置和宽度信息
            # 这可以防止拖拽过程中出现计算误差，确保右侧布局宽度真正固定不变
            self.last_x = new_window_x
            self.last_width = required_width
        # 从右侧拖拽（窗口右边界移动）
        elif width_diff != 0:
            # 从右侧拖拽时，固定左侧布局大小
            logger.info(f"从右侧拖拽：width_diff={width_diff}")
            
            # 保存当前左侧宽度
            original_left_width = self.left_width
            
            # 计算新的右侧宽度
            new_right_width = self.right_width + width_diff
            
            # 确保右侧宽度不会变得过小或过大
            if new_right_width > 1000:  # 添加最大宽度限制防止窗口过大
                self.right_width = 1000
                logger.warning(f"右侧宽度过大，保持最大宽度：{self.right_width}px")
            elif new_right_width > 100:  # 最小宽度限制
                self.right_width = new_right_width
            else:
                self.right_width = 100
                logger.warning(f"右侧宽度过小，保持最小宽度：{self.right_width}px")
                
            # 严格固定左侧布局大小，使其保持不变
            self.left_width = original_left_width  # 强制设置为原始值
            logger.info(f"从右侧拖拽时，左侧宽度固定：{self.left_width}px")
            
            # 根据左右两侧的宽度重新计算合理的窗口宽度
            # 确保窗口宽度不会小于左侧宽度+右侧宽度+边距
            required_width = self.left_width + self.right_width + 15  # 加上边距
            
            # 强制设置窗口宽度和位置
            self.root.geometry(f"{required_width}x{current_height}+{self.last_x}+{y}")
            logger.info(f"强制设置窗口宽度：{required_width}，左侧：{self.left_width}，右侧：{self.right_width}")
            
            # 更新当前宽度和last_width，防止循环计算错误
            current_width = required_width
            self.last_width = required_width  # 立即更新last_width，避免下一次计算出错
        
        # 统一更新框架的位置和大小，确保高度适应窗口变化
        # 调整高度计算，减小边距以避免底部留白
        available_height = current_height - 10
        self.left_frame.place_configure(x=5, y=5, width=self.left_width, height=available_height)
        self.right_frame.place_configure(x=self.left_width + 10, y=5, width=self.right_width, height=available_height)
        
        # 仅在不是从左侧或右侧拖拽的情况下更新记录的窗口位置和宽度
        # 这样可以保留拖拽操作中已经更新的位置和宽度信息
        if not (is_left_drag or (width_diff != 0)):
            self.last_x = x
            self.last_width = current_width
        
        # 判断是否是用户主动调整了窗口高度
        # 如果高度变化超过20像素，认为是用户主动调整
        # 增加阈值以更可靠地区分用户调整和系统自动调整
        if abs(current_height - self.last_height) > 20:
            self.last_height = current_height
            self.user_resized_height = True  # 标记为用户主动调整
        # 如果是首次加载且高度变化很小，可能是系统自动调整，不更新last_height
    
    def _on_closing(self):
        """窗口关闭时的处理"""
        # 保存当前状态
        self._save_state()
        # 停止播放
        self.stop_playback()
        # 退出pygame
        pygame.quit()
        # 退出Tkinter主循环
        self.root.destroy()
        
    def restore_default_layout(self):
        """恢复窗口的默认大小和位置，重置布局为初始状态"""
        try:
            # 计算默认窗口居中的坐标
            screen_width = self.root.winfo_screenwidth()
            screen_height = self.root.winfo_screenheight()
            x = (screen_width - self.DEFAULT_WIDTH) // 2
            y = (screen_height - self.DEFAULT_HEIGHT) // 2
            
            # 应用默认窗口大小和位置
            self.root.geometry(f"{self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}+{x}+{y}")
            logger.info(f"已恢复默认窗口位置: {x}, {y}, 大小: {self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}")
            
            # 更新记录的窗口位置和大小
            self.last_x = x
            self.last_y = y
            self.last_width = self.DEFAULT_WIDTH
            self.last_height = self.DEFAULT_HEIGHT
            
            # 标记为用户主动调整（通过恢复默认布局）
            self.user_resized_height = True
            
            # 计算左右框架的默认宽度（各占50%）
            available_width = self.DEFAULT_WIDTH - 40  # 考虑窗口边距和框架边距
            half_width = available_width // 2
            self.left_width = half_width
            self.right_width = half_width
            
            # 计算可用高度
            available_height = self.DEFAULT_HEIGHT - 10  # 考虑边距
            
            # 使用place_configure方法设置左右框架的位置和大小
            self.left_frame.place_configure(x=5, y=5, width=self.left_width, height=available_height)
            self.right_frame.place_configure(x=self.left_width + 10, y=5, width=self.right_width, height=available_height)
            
            # 刷新布局
            self.root.update_idletasks()
        except Exception as e:
            logger.error(f"恢复默认布局时出错: {str(e)}")
        
    def _save_state(self):
        """保存当前状态到配置文件"""
        try:
            # 获取当前窗口位置
            x = self.root.winfo_x()
            y = self.root.winfo_y()
            # 使用记录的大小值
            width = self.last_width
            
            # 只有当用户主动调整了窗口高度，才保存新的高度值
            # 这样可以避免系统自动调整导致窗口高度不断减少的问题
            if hasattr(self, 'user_resized_height') and self.user_resized_height:
                height = self.last_height
                logger.info(f"用户主动调整了窗口高度，保存新高度: {height}")
            else:
                # 如果用户没有主动调整，尝试从配置文件中读取原来的高度值
                try:
                    if os.path.exists(CONFIG_FILE):
                        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                            old_state = json.load(f)
                            if 'window_position' in old_state and 'height' in old_state['window_position']:
                                height = old_state['window_position']['height']
                                logger.info(f"用户未主动调整窗口高度，使用配置文件中的高度: {height}")
                            else:
                                height = self.last_height
                    else:
                        height = self.last_height
                except:
                    height = self.last_height
            
            # 检查current_file和current_folder是否一致，如果不一致且current_file存在，则更新current_folder
            if self.current_file and os.path.exists(self.current_file):
                file_folder = os.path.dirname(self.current_file)
                # 如果current_folder不存在，或者current_folder与file_folder不一致，则更新current_folder
                if not self.current_folder or self.current_folder != file_folder:
                    self.current_folder = file_folder
                    # 如果当前文件所在文件夹不在folders中，则添加它
                    if not self.folders.get(file_folder):
                        # 获取文件夹中的所有媒体文件
                        media_files = []
                        try:
                            for root_dir, _, files in os.walk(file_folder):
                                for file in files:
                                    file_path = os.path.join(root_dir, file)
                                    if any(file_path.lower().endswith(ext) for ext in media_extensions):
                                        media_files.append(file_path)
                            # 添加到folders字典
                            self.folders[file_folder] = media_files
                        except Exception as e:
                            logger.error(f"添加文件夹到folders时出错: {str(e)}")
            
            state = {
                'folders': self.folders,  # 保存所有添加的文件夹
                'current_folder': self.current_folder,  # 保存当前选中的文件夹
                'current_file': self.current_file,  # 保存当前播放的文件
                'current_position': self.current_position,  # 保存当前播放进度
                'current_song_index': self.current_song_index,  # 保存当前歌曲索引
                'current_playback_mode': self.current_playback_mode,  # 保存当前播放模式
                'volume': self.volume_var.get(),  # 保存音量设置
                'window_position': {
                    'x': x,
                    'y': y,
                    'width': width,
                    'height': height
                },
                'left_width': self.left_width,  # 保存左侧布局大小
                'right_width': self.right_width,  # 保存右侧布局大小
                'config': self.config  # 保存配置信息
            }
            
            # 确保配置文件所在目录存在
            config_dir = os.path.dirname(CONFIG_FILE)
            if config_dir and not os.path.exists(config_dir):
                os.makedirs(config_dir)
                
            # 写入配置文件
            with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
                json.dump(state, f, ensure_ascii=False, indent=2)
                
            logger.info(f"状态已保存到 {CONFIG_FILE}")
            
        except Exception as e:
            logger.error(f"保存状态时出错: {str(e)}")
            # 不显示错误消息，避免用户困扰
            
    def _load_config_data(self):
        """集中加载配置文件数据到内存"""
        try:
            if os.path.exists(CONFIG_FILE):
                with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                    self.config_data = json.load(f)
                logger.info("配置文件已成功加载到内存")
            else:
                # 如果配置文件不存在，使用空字典
                self.config_data = {}
                logger.info("配置文件不存在，使用空配置")
        except Exception as e:
            logger.error(f"加载配置文件时出错: {str(e)}")
            # 发生错误时也使用空字典
            self.config_data = {}
            
    def _load_window_config_from_memory(self):
        """从内存中的配置数据加载窗口位置和大小"""
        try:
            # 获取屏幕尺寸
            screen_width = self.root.winfo_screenwidth()
            screen_height = self.root.winfo_screenheight()
            
            # 默认居中显示
            default_x = (screen_width - self.DEFAULT_WIDTH) // 2
            default_y = (screen_height - self.DEFAULT_HEIGHT) // 2
            
            # 从内存配置数据中读取窗口位置和大小
            if self.config_data and 'window_position' in self.config_data and isinstance(self.config_data['window_position'], dict):
                window_pos = self.config_data['window_position']
                x = window_pos.get('x', default_x)
                y = window_pos.get('y', default_y)
                width = window_pos.get('width', self.DEFAULT_WIDTH)
                height = window_pos.get('height', self.DEFAULT_HEIGHT)
                
                # 确保窗口尺寸合理
                width = max(600, min(width, screen_width))
                height = max(400, min(height, screen_height))
                
                # 确保窗口在屏幕内
                x = max(0, min(x, screen_width - width))
                y = max(0, min(y, screen_height - height))
                
                logger.info(f"从内存配置加载窗口位置和大小: {width}x{height}+{x}+{y}")
                return x, y, width, height
            
            # 如果配置文件不存在或没有窗口位置信息，使用默认设置
            logger.info(f"配置文件不存在或没有窗口位置信息，使用默认设置: {self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}+{default_x}+{default_y}")
            return default_x, default_y, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
        except Exception as e:
            logger.error(f"从配置文件加载窗口位置和大小时出错: {str(e)}")
            # 出错时使用默认设置
            default_x = (self.root.winfo_screenwidth() - self.DEFAULT_WIDTH) // 2
            default_y = (self.root.winfo_screenheight() - self.DEFAULT_HEIGHT) // 2
            return default_x, default_y, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
    
    def _load_state(self):
        """从内存中的配置数据加载上次保存的状态"""
        try:
            if not self.config_data:
                logger.warning("内存中没有配置数据，跳过加载状态")
                return
                
            logger.info("从内存配置数据加载状态")
            
            # 加载配置信息
            if 'config' in self.config_data:
                self.config = self.config_data['config']
                logger.info(f"加载配置信息: {self.config}")
            
            # 恢复音量设置
            if 'volume' in self.config_data:
                volume = self.config_data['volume']
                # 确保音量值在有效范围内
                volume = max(0, min(100, volume))
                self.volume_var.set(volume)
                pygame.mixer.music.set_volume(volume / 100)  # 将0-100转换为0-1
                self.volume_display_label.config(text=f"{int(volume)}/100%")
                logger.info(f"恢复音量: {volume}")
            
            # 验证并恢复文件夹数据
            if 'folders' in self.config_data and isinstance(self.config_data['folders'], dict):
                # 检查文件夹是否仍然存在，只恢复存在的文件夹
                restored_folders = {}
                for folder_path, files in self.config_data['folders'].items():
                    if os.path.exists(folder_path):
                        # 检查文件是否仍然存在
                        valid_files = []
                        for file_path in files:
                            if os.path.exists(file_path) and any(file_path.lower().endswith(ext) for ext in media_extensions):
                                valid_files.append(file_path)
                        
                        if valid_files:
                            restored_folders[folder_path] = valid_files
                            # 添加到树状视图，但暂时不添加序号，后续统一处理
                            folder_name = os.path.basename(folder_path)
                            self.folder_tree.insert("", "end", text=folder_name, values=(folder_path,))
                        
                self.folders = restored_folders
                
                # 为所有已添加的文件夹添加序号
                children = self.folder_tree.get_children()
                total_folders = len(children)
                # 计算需要的位数（根据文件夹总数）
                num_digits = len(str(total_folders)) if total_folders > 0 else 1
                for idx, child in enumerate(children, 1):
                    # 使用局部变量，避免覆盖原始的folder_path
                    current_folder_path = self.folder_tree.item(child, "values")[0]
                    current_folder_name = os.path.basename(current_folder_path)
                    self.folder_tree.item(child, text=f"{idx:0{num_digits}d}. {current_folder_name}")
                
            # 恢复当前文件夹
            if 'current_folder' in self.config_data and self.config_data['current_folder'] in self.folders:
                self.current_folder = self.config_data['current_folder']
                self.current_folder_songs = self.folders[self.current_folder]
                
                # 填充歌曲列表（带序号）
                self.song_listbox.delete(0, tk.END)
                total_songs = len(self.current_folder_songs)
                # 计算需要的位数（根据歌曲总数）
                num_digits = len(str(total_songs)) if total_songs > 0 else 1
                for i, file_path in enumerate(self.current_folder_songs, 1):
                    self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
                    
                # 在树状视图中选中当前文件夹
                for item in self.folder_tree.get_children():
                    if self.folder_tree.item(item, "values")[0] == self.current_folder:
                        self.folder_tree.selection_set(item)
                        self.folder_tree.focus(item)
                        self.folder_tree.see(item)
                        break
                        
            # 恢复当前歌曲索引
            if 'current_song_index' in self.config_data:
                self.current_song_index = self.config_data['current_song_index']
                # 确保索引有效
                if self.current_song_index < 0 or self.current_song_index >= len(self.current_folder_songs or []):
                    self.current_song_index = -1
            
            # 恢复播放模式
            if 'current_playback_mode' in self.config_data and self.config_data['current_playback_mode'] in self.playback_modes:
                self.current_playback_mode = self.config_data['current_playback_mode']
                # 如果已经创建了播放模式按钮，则更新显示
                if hasattr(self, 'play_mode_btn'):
                    self.play_mode_btn.config(text=self.playback_modes[self.current_playback_mode]['icon'])
                    if hasattr(self, 'play_mode_tooltip'):
                        self.play_mode_tooltip.configure(text=self.playback_modes[self.current_playback_mode]['name']+":"+self.playback_modes[self.current_playback_mode]['description'])
                logger.info(f"恢复播放模式: {self.playback_modes[self.current_playback_mode]['name']}")
                    

            
            # 恢复当前播放的文件和进度，但不自动播放
            if 'current_file' in self.config_data and os.path.exists(self.config_data['current_file']):
                self.current_file = self.config_data['current_file']
                
                # 恢复播放进度
                if 'current_position' in self.config_data:
                    self.current_position = self.config_data['current_position']
                else:
                    self.current_position = 0
                
                # 根据文件类型决定显示歌词还是视频区域
                if any(self.current_file.lower().endswith(ext) for ext in video_extensions):
                    # 视频文件显示视频区域
                    self.show_video()
                    
                    # 初始化视频播放所需的资源，以便恢复后可以播放
                    try:
                        # 初始化视频相关属性
                        if not hasattr(self, 'is_video'):
                            self.is_video = False
                        if not hasattr(self, 'video_surface'):
                            self.video_surface = None
                        if not hasattr(self, 'video_rect'):
                            self.video_rect = None
                        if not hasattr(self, 'video_timer'):
                            self.video_timer = None
                        
                        self.is_video = True
                        
                        # 打开视频文件，创建用于播放的cap对象
                        import cv2
                        self.cap = cv2.VideoCapture(self.current_file)
                        if self.cap.isOpened():
                            self.fps = self.cap.get(cv2.CAP_PROP_FPS)
                            self.frame_count = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
                            self.total_length = self.frame_count / self.fps if self.fps > 0 else 0
                            logger.info(f"成功初始化视频资源: {self.current_file}")
                        else:
                            logger.warning(f"无法打开视频文件: {self.current_file}")
                        
                        # 更新文件信息显示
                        file_name = os.path.basename(self.current_file)
                        self.file_label.config(text=file_name)
                        
                        # 设置进度条为可拖动
                        self.progress_bar.config(state=tk.NORMAL)
                        
                        # 保持进度条始终为0-100的百分比模式
                        self.progress_bar.configure(to=100)
                        
                        # 更新时间显示标签
                        if self.total_length > 0:
                            self.time_display_label.config(text=f"00:00/{self.format_time(self.total_length)}")
                        else:
                            self.time_display_label.config(text="00:00/00:00")
                        
                        # 立即更新进度条显示，避免初始时长显示00:00的问题
                        self.update_progress()
                        
                        # 即使不播放，也显示当前进度对应的视频帧
                        if self.cap.isOpened():
                            try:
                                # 跳转到保存的播放位置或第一帧
                                target_frame = int(self.current_position * self.fps) if self.current_position > 0 else 0
                                self.cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
                                # 读取一帧
                                ret, frame = self.cap.read()
                                if ret:
                                    # 将OpenCV的BGR格式转换为RGB格式
                                    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                                    # 创建pygame Surface
                                    self.video_surface = pygame.surfarray.make_surface(rgb_frame)
                                    # 旋转和翻转以纠正方向
                                    self.video_surface = pygame.transform.rotate(self.video_surface, -90)
                                    self.video_surface = pygame.transform.flip(self.video_surface, True, False)
                                    
                                    # 保存当前进度信息，以便延迟显示时使用
                                    self.saved_video_position = self.current_position
                                    
                                    # 添加延迟，确保UI完全渲染后再显示视频帧，这样可以获取准确的Canvas尺寸
                                    self.root.after(100, self._delayed_display_video_frame)
                                    logger.info(f"已准备视频帧，将在UI渲染完成后显示: {self.current_position:.2f}秒")
                            except Exception as frame_e:
                                logger.error(f"准备视频帧时出错: {str(frame_e)}")
                        
                    except Exception as e:
                        logger.error(f"初始化视频资源时出错: {str(e)}")
                else:
                    # 音频文件显示歌词区域
                    self.show_lyrics()
                    # 解析歌词
                    self.parse_lyrics(self.current_file)
                
                # 设置文件信息显示
                file_name = os.path.basename(self.current_file)
                self.file_label.config(text=file_name)
                
                # 获取并显示文件长度，以便状态恢复后也能显示总时长
                try:
                    if self.current_file.lower().endswith('.mp3'):
                        audio = MP3(self.current_file)
                        length = audio.info.length
                    else:
                        # 对于其他格式，我们无法在不加载的情况下获取长度，所以这里不设置
                        length = 0
                    
                    if length > 0:
                        self.total_length = length
                        # 更新时间显示标签
                        if hasattr(self, 'time_display_label'):
                            current_time_str = self.format_time(self.current_position)
                            total_time_str = self.format_time(length)
                            self.time_display_label.config(text=f"{current_time_str}/{total_time_str}")
                        
                        # 更新进度条显示
                        if self.current_position > 0:
                            progress_percent = (self.current_position / self.total_length) * 100
                            self.progress_var.set(progress_percent)
                        
                        # 确保进度条状态为正常，避免显示为灰色
                        self.progress_bar.config(state=tk.NORMAL)
                except Exception as e:
                    logger.error(f"恢复文件长度信息时出错: {str(e)}")
                
                # 如果当前文件在当前文件夹中，高亮显示
                if self.current_folder_songs and self.current_file in self.current_folder_songs:
                    idx = self.current_folder_songs.index(self.current_file)
                    self.song_listbox.selection_clear(0, tk.END)
                    self.song_listbox.selection_set(idx)
                    self.song_listbox.see(idx)
                
            # 记录从配置文件加载的窗口位置和大小
            if 'window_position' in self.config_data and isinstance(self.config_data['window_position'], dict):
                try:
                    window_pos = self.config_data['window_position']
                    
                    # 保存恢复后的窗口位置和大小，但不再重新设置窗口大小，避免窗口闪烁
                    # 窗口位置和大小已经在初始化时通过_load_window_config_from_memory方法设置
                    self.last_width = window_pos.get('width', self.last_width)
                    self.last_height = window_pos.get('height', self.last_height)
                    
                    logger.info(f"在_load_state中记录窗口位置和大小: {self.last_width}x{self.last_height}")
                    
                    # 重置用户调整标记，因为这是从配置文件加载的高度
                    self.user_resized_height = False
                    
                    # 恢复左右侧布局的大小
                    if 'left_width' in self.config_data and isinstance(self.config_data['left_width'], (int, float)):
                        self.left_width = max(100, min(self.config_data['left_width'], self.last_width - 200))  # 确保有最小宽度和空间给右侧
                        logger.info(f"恢复左侧布局大小: {self.left_width}")
                    
                    if 'right_width' in self.config_data and isinstance(self.config_data['right_width'], (int, float)):
                        self.right_width = max(100, min(self.config_data['right_width'], self.last_width - self.left_width - 10))  # 确保有最小宽度
                        logger.info(f"恢复右侧布局大小: {self.right_width}")
                    
                    # 计算可用高度并应用左右侧布局大小
                    available_height = self.last_height - 10
                    self.left_frame.place_configure(x=5, y=5, width=self.left_width, height=available_height)
                    self.right_frame.place_configure(x=self.left_width + 10, y=5, width=self.right_width, height=available_height)
                    self.root.update_idletasks()
                except Exception as e:
                    logger.error(f"恢复窗口位置时出错: {str(e)}")
                
        except Exception as e:
            logger.error(f"加载状态时出错: {str(e)}")
            # 不显示错误消息，避免用户困扰
            # 不再删除配置文件，保留以便调试问题
            logger.info(f"配置文件保留在: {CONFIG_FILE}")
        
        # 确保搜索框在加载完所有状态后重置为初始状态
        # 无论之前保存了什么状态，强制显示提示文字
        self.search_entry.delete(0, tk.END)
        self.search_entry.insert(0, "搜索歌曲...")
        self.search_entry.config(foreground="gray50")
        # 禁用清除按钮
        self.clear_search_btn.config(state=tk.DISABLED)
    
    def on_search_focus_in(self, event):
        """当搜索框获得焦点时的处理"""
        # 当搜索框获得焦点时清除占位文本并更改颜色
        if self.search_entry.get() == "搜索歌曲...":
            self.search_entry.delete(0, tk.END)
            self.search_entry.config(foreground="black")
            
    def on_search_focus_out(self, event):
        """当搜索框失去焦点时的处理"""
        # 当搜索框失去焦点且内容为空时，恢复占位文本和灰色字体
        if not self.search_entry.get().strip():
            self.search_entry.delete(0, tk.END)
            self.search_entry.insert(0, "搜索歌曲...")
            self.search_entry.config(foreground="gray50")
    
    def on_search_enter(self, event):
        """当用户在搜索框中按Enter键时触发生成搜索结果"""
        self.search_songs()
    
    def on_search_text_change(self, event):
        """当搜索框文本变化时的处理，控制清除按钮状态"""
        # 检查搜索框内容是否为空且不是占位文本
        current_text = self.search_entry.get().strip()
        if current_text and current_text != "搜索歌曲...":
            self.clear_search_btn.config(state=tk.NORMAL)
        else:
            self.clear_search_btn.config(state=tk.DISABLED)
    
    def on_search_text_change_var(self, *args):
        """通过StringVar跟踪文本变化时的处理"""
        # 调用主处理函数
        self.on_search_text_change(None)
    
    def search_songs(self):
        """核心搜索方法"""
        search_text = self.search_entry.get().strip().lower()
        
        if not search_text or search_text == "搜索歌曲...":
            # 如果搜索文本为空，不执行搜索
            return
        
        # 如果还没有进入搜索模式，保存当前的歌曲列表
        if not self.is_search_mode:
            self.original_songs = self.current_folder_songs.copy()
            self.is_search_mode = True
        
        # 执行搜索
        self.search_results = []
        
        # 在所有已添加的文件夹中搜索
        for folder_path, songs in self.folders.items():
            for song_path in songs:
                song_name = os.path.basename(song_path).lower()
                if search_text in song_name:
                    # 保存歌曲路径和所属文件夹
                    self.search_results.append((song_path, folder_path))
        
        # 显示搜索结果
        self.song_listbox.delete(0, tk.END)
        
        if not self.search_results:
            # 如果没有搜索结果，显示提示
            self.song_listbox.insert(tk.END, "没有找到匹配的歌曲")
        else:
            # 显示搜索结果，带序号和文件夹信息
            total_results = len(self.search_results)
            num_digits = len(str(total_results)) if total_results > 0 else 1
            for i, (song_path, folder_path) in enumerate(self.search_results, 1):
                folder_name = os.path.basename(folder_path)
                song_name = os.path.basename(song_path)
                self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. [{folder_name}] {song_name}")
        
        # 启用清除按钮
        self.clear_search_btn.config(state=tk.NORMAL)
        
        # 显示搜索结果数量提示
        self.show_temp_message(f"找到 {len(self.search_results)} 个匹配结果", "lightblue", "black")
    
    def clear_search_results(self):
        """清除搜索结果，返回原始歌曲列表"""
        # 无论是否在搜索模式下，都需要清空搜索输入框
        self.search_entry.delete(0, tk.END)
        self.search_entry.insert(0, "搜索歌曲...")
        self.search_entry.config(foreground="gray50")
        
        if self.is_search_mode:
            # 恢复原始歌曲列表
            if self.current_folder and self.current_folder in self.folders:
                self.current_folder_songs = self.folders[self.current_folder].copy()
                
                # 重新显示当前文件夹的歌曲
                self.song_listbox.delete(0, tk.END)
                total_songs = len(self.current_folder_songs)
                num_digits = len(str(total_songs)) if total_songs > 0 else 1
                for i, file_path in enumerate(self.current_folder_songs, 1):
                    self.song_listbox.insert(tk.END, f"{i:0{num_digits}d}. {os.path.basename(file_path)}")
            
            # 重置搜索模式标志
            self.is_search_mode = False
            self.search_results = []
            
            # 禁用清除按钮
            self.clear_search_btn.config(state=tk.DISABLED)
    
    def original_play_selected_song(self):
        """原始的播放选中歌曲方法"""
        # 播放选中的歌曲
        selection = self.song_listbox.curselection()
        if selection and self.current_folder_songs:
            index = selection[0]
            file_path = self.current_folder_songs[index]
            self.current_song_index = index
            self.play_file(file_path)
    
    def play_selected_song(self):
        """重写播放选中歌曲方法，支持搜索模式"""
        if self.is_search_mode and self.search_results:
            selection = self.song_listbox.curselection()
            if not selection:
                return
            
            index = selection[0]
            if 0 <= index < len(self.search_results):
                song_path, folder_path = self.search_results[index]
                
                # 切换到歌曲所在的文件夹
                if folder_path != self.current_folder:
                    self.current_folder = folder_path
                    self.current_folder_songs = self.folders[folder_path].copy()
                
                # 查找歌曲在当前文件夹中的索引
                for i, path in enumerate(self.current_folder_songs):
                    if path == song_path:
                        self.current_song_index = i
                        break
                
                # 播放歌曲
                self.play_file(song_path)
        else:
            # 调用原始的播放方法
            self.original_play_selected_song()
    
    def original_open_song_folder(self):
        """原始的打开歌曲文件夹方法"""
        # 打开选中歌曲所在的文件夹并定位到文件
        selection = self.song_listbox.curselection()
        if selection and self.current_folder_songs:
            index = selection[0]
            file_path = self.current_folder_songs[index]
            folder_path = os.path.dirname(file_path)
            try:
                # Windows系统下使用explorer打开文件夹并选中文件
                if os.name == 'nt':
                    # /select参数可以打开文件夹并选中指定文件
                    subprocess.Popen(['explorer', '/select,', file_path])
                # macOS下使用open打开文件夹并选中文件
                elif os.name == 'posix' and 'darwin' in platform.system().lower():
                    # -R参数可以在Finder中显示文件
                    subprocess.Popen(['open', '-R', file_path])
                # Linux下尝试不同的文件管理器
                elif os.name == 'posix' and 'linux' in platform.system().lower():
                    # 尝试多种常见的Linux文件管理器
                    file_managers = ['nautilus', 'nemo', 'caja', 'dolphin', 'thunar']
                    opened = False
                    for fm in file_managers:
                        try:
                            if fm in ['nautilus', 'nemo', 'caja']:
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'dolphin':
                                subprocess.Popen([fm, '--select', file_path])
                            elif fm == 'thunar':
                                subprocess.Popen([fm, '--select', file_path])
                            opened = True
                            break
                        except FileNotFoundError:
                            continue
                    # 如果所有文件管理器都失败，回退到打开文件夹
                    if not opened:
                        subprocess.Popen(['xdg-open', folder_path])
                # 其他系统回退到打开文件夹
                else:
                    subprocess.Popen(['xdg-open', folder_path])
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件夹: {str(e)}")
    
    def _play_video_audio_externally(self):
        """使用外部工具播放视频音频"""
        try:
            logger.info(f"尝试使用外部工具播放视频音频: {self.current_file}")
            
            # 从配置文件中获取ffplay路径，如果没有则使用默认的"ffplay"
            ffplay_path = "ffplay"  # 默认值，假设在PATH中
            if 'ffplay_path' in self.config:
                ffplay_path = self.config['ffplay_path']
                logger.info(f"从配置文件中读取ffplay路径: {ffplay_path}")
            
            # 在启动新进程前，确保终止所有可能正在运行的ffplay进程
            self._terminate_all_ffplay_processes()
            
            # 首先检查ffplay是否可用（无论是在PATH中还是通过配置的路径）
            logger.info(f"检查ffplay是否可用: {ffplay_path}")
            try:
                # 尝试执行ffplay -version来验证它是否可用
                version_check = subprocess.Popen(
                    [ffplay_path, "-version"],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=False,
                    creationflags=0x08000000  # CREATE_NO_WINDOW
                )
                stdout, stderr = version_check.communicate(timeout=2)
                if version_check.returncode == 0:
                    logger.info(f"ffplay已找到并可访问: {ffplay_path}")
                else:
                    logger.error(f"ffplay找到了，但执行失败: {stderr.decode('utf-8', errors='ignore')}")
            except FileNotFoundError:
                logger.error(f"找不到ffplay可执行文件: {ffplay_path}")
                # 如果是默认路径失败，提示用户可以在配置文件中手动设置路径
                if ffplay_path == "ffplay":
                    # 提供更具体的配置文件修改指南
                    config_edit_guide = f"""在系统PATH中找不到ffplay可执行文件。\n\n解决方案:\n1. 安装ffmpeg并添加到系统PATH中\n2. 或在配置文件中手动指定ffplay的完整路径\n   方法: \n   - 打开配置文件: {CONFIG_FILE}\n   - 在'config'对象中添加或修改: \n     \"ffplay_path\": \"D:\\ffmpeg\\bin\\ffplay.exe\" (替换为实际路径)\n   - 保存文件后重启音频播放器"""
                    raise Exception(config_edit_guide)
                else:
                    raise Exception(f"在指定路径找不到ffplay可执行文件: {ffplay_path}\n请检查配置文件中的路径是否正确")
            except subprocess.TimeoutExpired:
                logger.warning("ffplay版本检查超时，但可能仍然可用")
            except Exception as version_e:
                logger.error(f"ffplay版本检查异常: {str(version_e)}")
            
            # 根据操作系统选择合适的命令行工具
            if platform.system() == "Windows":
                # 在Windows上使用ffplay（需要安装ffmpeg）
                # 修改命令以隐藏控制台窗口，增加更多参数确保音频播放
                # 使用-af volume参数代替-volume参数，支持更灵活的音量控制
                # 将0-100的音量值映射到0-1的倍数范围
                
                # 先检查是否有直接设置的音量值
                if hasattr(self, 'current_volume'):
                    volume_percent = self.current_volume
                    logger.info(f"使用current_volume: {volume_percent}%")
                elif hasattr(self, 'volume_var'):
                    # 尝试从volume_var获取
                    try:
                        volume_percent = int(self.volume_var.get())
                        logger.info(f"使用volume_var: {volume_percent}%")
                    except:
                        # 如果失败，使用默认音量70%
                        volume_percent = 70
                        logger.warning("无法获取volume_var，使用默认音量70%")
                else:
                    # 默认音量70%
                    volume_percent = 70
                    logger.warning("没有找到音量设置，使用默认音量70%")
                
                # 确保音量在有效范围内
                volume_percent = max(0, min(100, volume_percent))
                
                # 对于音量为0的特殊处理：使用-∞dB确保完全静音
                if volume_percent == 0:
                    volume_filter = "volume=-100dB"
                    logger.info("音量设为0，使用-100dB确保完全静音")
                else:
                    volume_multiplier = volume_percent / 100.0
                    volume_filter = f"volume={volume_multiplier}"
                    logger.info(f"使用音量倍数: {volume_multiplier}启动ffplay")
                
                # 使用-nodisp参数隐藏窗口，同时实现播放状态下音量变化时重启ffplay的逻辑
                cmd = [ffplay_path, "-nodisp", "-autoexit", "-af", volume_filter, "-hide_banner", "-loglevel", "quiet"]
                if self.current_position > 0:
                    cmd.extend(["-ss", str(self.current_position)])
                cmd.append(self.current_file)
                
                logger.info(f"执行ffplay命令: {' '.join(cmd)}")
                
                logger.info(f"执行ffplay命令: {' '.join(cmd)}")
                
                # 启动外部进程播放音频，添加额外参数隐藏窗口和捕获输出
                startupinfo = subprocess.STARTUPINFO()
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                startupinfo.wShowWindow = subprocess.SW_HIDE
                
                # 再次检查是否有其他ffplay进程正在运行
                self._terminate_all_ffplay_processes()
                
                logger.info(f"准备启动ffplay进程，当前文件: {self.current_file}")
                self.external_audio_process = subprocess.Popen(
                    cmd,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=False,
                    startupinfo=startupinfo,
                    creationflags=0x08000000  # CREATE_NO_WINDOW
                )
                logger.info(f"ffplay进程已启动，PID: {self.external_audio_process.pid}")
                
                # 添加一个短暂的延迟，然后检查进程是否还在运行
                import time
                time.sleep(0.5)
                
                # 检查进程是否还在运行
                if self.external_audio_process.poll() is None:
                    logger.info(f"外部音频播放器已成功启动并正在运行，PID: {self.external_audio_process.pid}")
                    # 设置同步开始时间，用于视频和音频同步
                    self.sync_start_time = time.time() - self.current_position
                    # 确保播放开始时间正确设置，用于update_progress方法中的时间计算
                    self.playback_start_time = self.sync_start_time
                else:
                    # 进程已经退出，获取错误信息
                    stderr = self.external_audio_process.stderr.read().decode('utf-8', errors='ignore')
                    logger.error(f"外部音频播放器启动失败，错误信息: {stderr}")
                    raise Exception(f"ffplay启动失败，可能的错误: {stderr}")
        except Exception as e:
            logger.error(f"外部音频播放失败: {str(e)}")
            # 更详细的错误信息，帮助用户排查问题
            error_msg = f"外部ffplay播放失败: {str(e)}\n\n" \
                      f"请确认:\n" \
                      f"1. 已正确安装ffmpeg(包含ffplay)\n" \
                      f"2. ffplay已添加到系统PATH环境变量\n" \
                      f"3. 重启计算机使PATH变更生效\n" \
                      f"4. 打开命令提示符，输入'ffplay -version'验证安装是否成功\n" \
                      f"5. 确认视频文件路径正确且文件没有损坏\n\n" \
                      f"PATH排查提示:\n" \
                      f"- 打开命令提示符，输入'echo %PATH%'检查ffmpeg目录是否在PATH中\n" \
                      f"- 输入'where ffplay'查看系统是否能找到ffplay可执行文件\n" \
                      f"- 若找不到，请手动将ffmpeg的bin目录添加到PATH中\n" \
                      f"- 添加后必须重启所有程序和命令提示符窗口" 
            raise Exception(error_msg)
    
    def _perform_delayed_seek(self, position):
        """延迟执行的跳转操作，用于优化拖动体验"""
        try:
            logger.info(f"执行延迟跳转: {position:.2f} 秒")
            
            # 确保播放状态
            if not self.is_playing:
                logger.info("当前未处于播放状态，取消跳转")
                return
            
            # 终止当前播放的外部进程
            self._terminate_external_process()
            
            # 更新当前位置
            self.current_position = position
            self.jump_position = position
            self.just_jumped = True
            self.playback_start_time = time.time() - position
            
            # 使用新的位置重启ffplay进程
            logger.info(f"ffplay延迟跳转到 {position:.2f} 秒")
            self._play_video_audio_externally()
        except Exception as e:
            logger.error(f"延迟跳转执行失败: {str(e)}")
            
    def _terminate_all_ffplay_processes(self):
        """终止系统中所有正在运行的ffplay进程"""
        try:
            logger.info("尝试终止所有可能正在运行的ffplay进程")
            
            # 首先尝试终止我们自己启动的外部进程
            if hasattr(self, 'external_audio_process') and self.external_audio_process is not None:
                if self.external_audio_process.poll() is None:
                    logger.info(f"终止自己的ffplay进程，PID: {self.external_audio_process.pid}")
                    try:
                        self.external_audio_process.terminate()
                        # 等待进程终止
                        self.external_audio_process.wait(timeout=1.0)
                        logger.info(f"ffplay进程已成功终止，PID: {self.external_audio_process.pid}")
                    except subprocess.TimeoutExpired:
                        logger.warning(f"ffplay进程终止超时，尝试强制终止，PID: {self.external_audio_process.pid}")
                        try:
                            self.external_audio_process.kill()
                            logger.info(f"ffplay进程已被强制终止，PID: {self.external_audio_process.pid}")
                        except Exception as e:
                            logger.error(f"强制终止ffplay进程失败: {str(e)}")
                    except Exception as e:
                        logger.error(f"终止ffplay进程时出错: {str(e)}")
                else:
                    logger.info(f"自己的ffplay进程已经终止，PID: {self.external_audio_process.pid}")
                
                # 重置外部进程引用
                self.external_audio_process = None
            
            # 在Windows系统上，使用taskkill终止所有ffplay进程
            if platform.system() == "Windows":
                import os
                try:
                    # 静默终止所有ffplay进程，不显示命令窗口
                    os.system("taskkill /F /IM ffplay.exe /T >nul 2>&1")
                    logger.info("已尝试终止系统中所有ffplay进程")
                except Exception as e:
                    logger.warning(f"尝试终止系统中所有ffplay进程时出错: {str(e)}")
        except Exception as e:
            logger.error(f"终止ffplay进程时发生异常: {str(e)}")
    
    def _start_video_audio_sync(self):
        """启动简单的视频音频同步机制"""
        # 这里我们创建一个简单的同步机制，使用时间戳来模拟音频播放
        if not hasattr(self, 'sync_start_time'):
            self.sync_start_time = time.time() - self.current_position
        logger.info("简单音频同步机制已启动")
    
    def update_video(self):
        """更新视频帧"""
        if self.is_video and self.is_playing:
            try:
                # 更新当前播放位置
                if self.playback_start_time is not None:
                    self.current_position = time.time() - self.playback_start_time
                    
                # 获取当前视频位置并与音频同步
                try:
                    # 检查是否使用了外部音频播放器
                    if hasattr(self, 'external_audio_process'):
                        # 对于外部播放器，我们简单地使用时间戳进行同步
                        audio_pos = time.time() - self.playback_start_time
                    elif pygame.mixer.music.get_busy():
                        # 对于pygame音乐播放器
                        audio_pos = pygame.mixer.music.get_pos() / 1000  # 转换为秒
                    else:
                        # 使用简单的时间戳同步
                        audio_pos = time.time() - self.sync_start_time if hasattr(self, 'sync_start_time') else self.current_position
                    
                    # 确保audio_pos不为None且有效
                    if audio_pos is not None:
                        # 更新视频位置以匹配音频
                        if hasattr(self, 'cap') and self.cap is not None:
                            current_video_pos = self.cap.get(cv2.CAP_PROP_POS_MSEC) / 1000  # 转换为秒
                            
                            # 如果音频和视频不同步，调整视频位置
                            if abs(audio_pos - current_video_pos) > 0.5:  # 如果差异超过0.5秒
                                self.cap.set(cv2.CAP_PROP_POS_MSEC, audio_pos * 1000)
                except Exception as sync_e:
                    logger.error(f"同步过程中出错: {str(sync_e)}")
                    # 继续执行，不中断视频播放
                
                if hasattr(self, 'cap') and self.cap is not None:
                    # 使用OpenCV读取视频帧
                    ret, frame = self.cap.read()
                    if ret:
                        # 将OpenCV的BGR格式转换为RGB格式
                        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                        # 获取帧的高度和宽度
                        height, width, _ = rgb_frame.shape
                        
                        # 创建pygame Surface
                        self.video_surface = pygame.surfarray.make_surface(rgb_frame)
                        # 旋转和翻转以纠正方向
                        self.video_surface = pygame.transform.rotate(self.video_surface, -90)
                        self.video_surface = pygame.transform.flip(self.video_surface, True, False)
                        
                        # 将pygame Surface转换为PhotoImage
                        self._display_video_frame()
                    else:
                        # 视频播放完毕
                        self.play_next()
                elif self.video_surface:
                    # 使用pygame创建的视频表面
                    self._display_video_frame()
                    
                # 检查是否到达视频末尾
                if self.current_position >= self.total_length:
                    self.play_next()
                else:
                    # 继续更新视频帧，优化帧延迟计算
                    frame_delay = int(1000 / self.fps) if hasattr(self, 'fps') and self.fps > 0 else 40
                    # 确保最小的帧延迟，避免过于频繁的更新
                    frame_delay = max(20, frame_delay)  # 至少20ms更新一次，约50fps
                    self.video_timer = self.root.after(frame_delay, self.update_video)
                    
                    # 更新进度
                    self.update_progress()
            except Exception as e:
                logger.error(f"更新视频帧时出错: {str(e)}")
                
    def show_temp_message(self, message, bg_color="red", fg_color="white", duration=2000, x=0.5, y=0.1):
        """显示自动消失的临时提示信息
        
        参数:
        message: 要显示的消息文本
        bg_color: 背景颜色，默认为红色
        fg_color: 前景颜色，默认为白色
        duration: 显示持续时间（毫秒），默认为2000
        x, y: 提示信息位置（相对坐标），默认为窗口顶部中心
        """
        temp_label = ttk.Label(self.root, text=message, background=bg_color, foreground=fg_color, padding=(10, 5))
        temp_label.place(relx=x, rely=y, anchor=tk.CENTER)
        self.root.after(duration, temp_label.destroy)
    
    def _delayed_display_video_frame(self):
        """延迟显示视频帧，确保UI完全渲染后再显示，解决视频帧不居中的问题"""
        try:
            if self.video_surface:
                self._display_video_frame()
                logger.info(f"已延迟显示视频帧: {getattr(self, 'saved_video_position', 0):.2f}秒")
        except Exception as e:
            logger.error(f"延迟显示视频帧时出错: {str(e)}")
    
    def _display_video_frame(self):
        """将视频帧显示在Canvas上"""
        if self.video_surface:
            try:
                # 获取Canvas的尺寸
                canvas_width = self.video_canvas.winfo_width()
                canvas_height = self.video_canvas.winfo_height()
                
                # 如果Canvas尺寸为0，使用默认尺寸
                if canvas_width <= 1: canvas_width = 320
                if canvas_height <= 1: canvas_height = 240
                
                # 调整视频帧大小以适应Canvas
                video_width, video_height = self.video_surface.get_size()
                aspect_ratio = video_width / video_height
                
                # 计算新的尺寸，保持宽高比
                if canvas_width / canvas_height > aspect_ratio:
                    # 以高度为基准
                    new_height = canvas_height
                    new_width = int(new_height * aspect_ratio)
                else:
                    # 以宽度为基准
                    new_width = canvas_width
                    new_height = int(new_width / aspect_ratio)
                
                # 调整视频帧大小，使用平滑缩放算法提高清晰度
                try:
                    # 使用pygame的平滑缩放算法
                    resized_surface = pygame.transform.smoothscale(self.video_surface, (new_width, new_height))
                except:
                    # 如果平滑缩放不可用，回退到普通缩放
                    resized_surface = pygame.transform.scale(self.video_surface, (new_width, new_height))
                
                # 将pygame Surface转换为PhotoImage
                # 这部分需要使用PIL来处理图像转换
                try:
                    from PIL import Image, ImageTk
                    # 获取像素数据
                    data = pygame.image.tostring(resized_surface, "RGB")
                    # 创建PIL图像
                    image = Image.frombytes("RGB", resized_surface.get_size(), data)
                    # 创建PhotoImage
                    self.video_photo = ImageTk.PhotoImage(image)
                    
                    # 计算居中位置
                    x = (canvas_width - new_width) // 2
                    y = (canvas_height - new_height) // 2
                    
                    # 更新Canvas上的图像
                    if self.video_canvas_id:
                        self.video_canvas.delete(self.video_canvas_id)
                    self.video_canvas_id = self.video_canvas.create_image(x, y, anchor=tk.NW, image=self.video_photo)
                except Exception as e:
                    # 如果PIL不可用，显示简单的占位符
                    if self.video_canvas_id:
                        self.video_canvas.delete(self.video_canvas_id)
                    # 在Canvas上显示文本
                    self.video_canvas_id = self.video_canvas.create_text(
                        canvas_width//2, canvas_height//2,
                        text="视频播放中", fill="white", font=("SimHei", 16)
                    )
            except Exception as e:
                logger.error(f"显示视频帧时出错: {str(e)}")
    
    def show_video(self):
        """显示视频区域，隐藏歌词"""
        # 隐藏歌词显示
        self.lyrics_text.pack_forget()
        # 显示视频画布
        self.video_canvas.pack(fill=tk.BOTH, expand=True)
        # 更改媒体区域标题
        self.media_frame.config(text="视频")
    
    def show_lyrics(self):
        """显示歌词区域，隐藏视频"""
        # 隐藏视频画布
        self.video_canvas.pack_forget()
        # 显示歌词文本框
        self.lyrics_text.pack(fill=tk.BOTH, expand=True)
        # 更改媒体区域标题
        self.media_frame.config(text="歌词")
    
    def open_song_folder(self):
        """重写打开歌曲文件夹方法，支持搜索模式"""
        if self.is_search_mode and self.search_results:
            selection = self.song_listbox.curselection()
            if not selection:
                return
            
            index = selection[0]
            if 0 <= index < len(self.search_results):
                song_path, _ = self.search_results[index]
                folder_path = os.path.dirname(song_path)
                try:
                    # Windows系统下使用explorer打开文件夹并选中文件
                    if os.name == 'nt':
                        # /select参数可以打开文件夹并选中指定文件
                        subprocess.Popen(['explorer', '/select,', song_path])
                    # macOS下使用open打开文件夹并选中文件
                    elif os.name == 'posix' and 'darwin' in platform.system().lower():
                        # -R参数可以在Finder中显示文件
                        subprocess.Popen(['open', '-R', song_path])
                    # Linux下尝试不同的文件管理器
                    elif os.name == 'posix' and 'linux' in platform.system().lower():
                        # 尝试多种常见的Linux文件管理器
                        file_managers = ['nautilus', 'nemo', 'caja', 'dolphin', 'thunar']
                        opened = False
                        for fm in file_managers:
                            try:
                                if fm in ['nautilus', 'nemo', 'caja']:
                                    subprocess.Popen([fm, '--select', song_path])
                                elif fm == 'dolphin':
                                    subprocess.Popen([fm, '--select', song_path])
                                elif fm == 'thunar':
                                    subprocess.Popen([fm, '--select', song_path])
                                opened = True
                                break
                            except FileNotFoundError:
                                continue
                        # 如果所有文件管理器都失败，回退到打开文件夹
                        if not opened:
                            subprocess.Popen(['xdg-open', folder_path])
                    # 其他系统回退到打开文件夹
                    else:
                        subprocess.Popen(['xdg-open', folder_path])
                except Exception as e:
                    messagebox.showerror("错误", f"无法打开文件夹: {str(e)}")
        else:
            # 调用原始的打开文件夹方法
            self.original_open_song_folder()
            
    def open_lyrics_folder(self):
        """打开选中歌曲对应的歌词文件所在的位置并定位到文件"""
        if self.is_search_mode and self.search_results:
            selection = self.song_listbox.curselection()
            if not selection:
                return
            
            index = selection[0]
            if 0 <= index < len(self.search_results):
                song_path, _ = self.search_results[index]
                self._open_lyrics_for_song(song_path)
        else:
            # 普通模式处理
            selection = self.song_listbox.curselection()
            if selection and self.current_folder_songs:
                index = selection[0]
                file_path = self.current_folder_songs[index]
                self._open_lyrics_for_song(file_path)
    
    def _open_lyrics_for_song(self, song_path):
        """打开指定歌曲对应的歌词文件所在的位置并定位到文件"""
        # 构建歌词文件路径
        song_dir = os.path.dirname(song_path)
        file_name = os.path.basename(song_path)
        base_name = os.path.splitext(file_name)[0]
        lrc_folder = os.path.join(song_dir, "lyrics")
        lrc_path = os.path.join(lrc_folder, base_name + '.lrc')
        
        try:
            # 检查歌词文件夹是否存在
            if not os.path.exists(lrc_folder):
                # 显示自动消失的临时提示信息
                self.show_temp_message(f"歌词文件夹不存在: {lrc_folder}", "red", "white")
                return
            
            # Windows系统下使用explorer打开文件夹并选中文件
            if os.name == 'nt':
                if os.path.exists(lrc_path):
                    # /select参数可以打开文件夹并选中指定文件
                    subprocess.Popen(['explorer', '/select,', lrc_path])
                else:
                    # 如果歌词文件不存在，只打开歌词文件夹
                    subprocess.Popen(['explorer', lrc_folder])
                    # 显示自动消失的临时提示信息
                    self.show_temp_message(f"未找到歌词文件: {lrc_path}", "red", "white")
            # macOS下使用open打开文件夹并选中文件
            elif os.name == 'posix' and 'darwin' in platform.system().lower():
                if os.path.exists(lrc_path):
                    # -R参数可以在Finder中显示文件
                    subprocess.Popen(['open', '-R', lrc_path])
                else:
                    # 如果歌词文件不存在，只打开歌词文件夹
                    subprocess.Popen(['open', lrc_folder])
                    # 显示自动消失的临时提示信息
                    self.show_temp_message(f"未找到歌词文件: {lrc_path}", "red", "white")
            # Linux下尝试不同的文件管理器
            elif os.name == 'posix' and 'linux' in platform.system().lower():
                # 尝试多种常见的Linux文件管理器
                file_managers = ['nautilus', 'nemo', 'caja', 'dolphin', 'thunar']
                opened = False
                if os.path.exists(lrc_path):
                    for fm in file_managers:
                        try:
                            if fm in ['nautilus', 'nemo', 'caja']:
                                subprocess.Popen([fm, '--select', lrc_path])
                            elif fm == 'dolphin':
                                subprocess.Popen([fm, '--select', lrc_path])
                            elif fm == 'thunar':
                                subprocess.Popen([fm, '--select', lrc_path])
                            opened = True
                            break
                        except FileNotFoundError:
                            continue
                
                # 如果歌词文件不存在或所有文件管理器都失败，回退到打开文件夹
                if not opened:
                    subprocess.Popen(['xdg-open', lrc_folder])
                    if not os.path.exists(lrc_path):
                        # 显示自动消失的临时提示信息
                        self.show_temp_message(f"未找到歌词文件: {lrc_path}", "red", "white")
            # 其他系统回退到打开文件夹
            else:
                subprocess.Popen(['xdg-open', lrc_folder])
                if not os.path.exists(lrc_path):
                    # 显示自动消失的临时提示信息
                    self.show_temp_message(f"未找到歌词文件: {lrc_path}", "red", "white")
        except Exception as e:
            # 显示自动消失的临时提示信息
            self.show_temp_message(f"无法打开歌词文件夹: {str(e)}", "red", "white")

if __name__ == "__main__":
    root = tk.Tk()
    app = AudioVideoPlayer(root)
    root.mainloop()